React dnd-不确定如何为相同类型的嵌套组件启用拖放

React-dnd - Not sure how to get drag and drop to fire for nested components of the same type

本文关键字:嵌套 组件 启用 拖放 同类型 不确定 dnd- React      更新时间:2023-09-26

我正在尝试构建一个可使用react dnd完全拖动的react嵌套列表组件。我有一个包含项目组件的菜单组件,每个项目也可以包含其他项目组件。不幸的是,我不知道如何使具有子节点或其任何子节点的节点可拖动。现在我已经得到了没有childrendraggable的节点,这是一个很好的开始,但后来失败了:

类型错误:节点未定义

每当我试图拖着任何孩子的时候。我使用了react dnd简单可排序示例作为参考,但它不包含任何嵌套。

以下是我目前所拥有的:

菜单.js

//React DnD
var DragDropContext = require('react-dnd').DragDropContext;
var HTML5Backend = require('react-dnd-html5-backend');
//Item
var Item = require('./item');
var Menu = React.createClass({
    getInitialState() {
        return {
            currentNode: this.props.data,
            items: [],
        };
    },
    _clicked(child) {
        this.setState({
            currentNode: child,
        });
    },
    componentDidMount() {
        this._updateData();
    },
   _updateData: function(list) {
        var $this = this;
        if(_.isUndefined(list)){
            var children = $this.props.data.children;
        }
        else{
            if(_.isEmpty(list.children)){
                var children = null;
            }
            else{
                var children = list.children;
            }
        }
        if(children != null){
            var items = children.map(function(item, i) {
                return (<Item
                    key={item.id}
                    id={item.id}
                    child={item}
                    showChildren={this.props.showFirstChildren}
                    clickable={true}
                    onClick={$this._updateData}
                    swapItems={this.swapItems}
                />);
            }.bind(this));
            
            if(!_.isUndefined(list)){
                $this.setState({
                    currentNode: list,
                });
            }
            
            $this.setState({ 
                items: items,
            });
            
        }
    },
    compareItems: function(item1, item2){
        return item1.position - item2.position;
    },
    swapItems: function(id1, id2) {
        var $this = this;
        var items = this.state.currentNode.children;
        var item1 = items.filter(function(i){return i.id === id1})[0];
        var item2 = items.filter(function(i){return i.id === id2})[0];
        var item1Pos = item1.position;
        item1.position = item2.position;
        item2.position = item1Pos;
        items.sort(this.compareItems);
        var newItems = items.map(function(item, i) {
            return (<Item
                key={item.id}
                id={item.id}
                child={item}
                showChildren={this.props.showFirstChildren}
                clickable={true}
                onClick={$this._updateData}
                swapItems={this.swapItems}
            />);
        }.bind(this));
        this.setState({
            items: newItems,
        });
    },
    render() {
        return (
            <div>
                {this.state.currentNode.name}
                <ul>
                    {this.state.items}
                </ul>
            </div>
        );
    },
});
module.exports = DragDropContext(HTML5Backend)(Menu);

Item.js

var React = require('react');
var ReactDnD = require('react-dnd');
var ItemTypes = {
  ITEM: 'item'
};
var itemSource = {
  beginDrag: function (props) {
    return {
        id: props.id,
    };
  }
};
var itemTarget = {
    hover: function(props, monitor) {
        var draggedId = monitor.getItem().id;
        if (draggedId !== props.id) {
            props.swapItems(draggedId, props.id);
        }
    }
};
var Item = React.createClass({
    propTypes: {
        connectDropTarget: React.PropTypes.func,
        connectDragSource: React.PropTypes.func,
        isDragging: React.PropTypes.bool,
        id: React.PropTypes.any,
        swapItems: React.PropTypes.func,
    },
    _clicked(child) {
        this.props.onClick(child);
    },
    render() {
        var $this = this;
        var connectDragSource = $this.props.connectDragSource;
        var isDragging = $this.props.isDragging;
        var connectDropTarget = $this.props.connectDropTarget;
        var child = this.props.child;
        var childrenWrapper = null;
        if (child.children && this.props.showChildren) {
            var children = child.children.map(function(item, i) {
                return (<Item
                    key={item.id}
                    id={item.id}
                    showChildren={false}
                    child={item}
                    onClick={$this._clicked}
                    clickable={true}
                    //passing these items along because otherwise it throws errors
                    connectDropTarget={$this.props.connectDropTarget}
                    connectDragSource={$this.props.connectDragSource}
                    swapItems={$this.props.swapItems}
                />);
            });
            childrenWrapper = (
                <ul>
                    {children}
                </ul>
            );
        }
        var style = {
            cursor: 'move',
            opacity: this.props.isDragging ? 0 : 1
        };
        if (child.children && this.props.clickable) {
            return connectDragSource(connectDropTarget(
                 <li style={style} key={child.id} ><a onClick={$this._clicked.bind(null, child)}>{child.title}</a>{childrenWrapper}</li>
            ));
        } else {
            return connectDragSource(connectDropTarget(
                 <li style={style} key={child.id}><a>{child.title}{childrenWrapper}</a></li>
            ));
        }
    },
});
var DragSourceDecorator = ReactDnD.DragSource(ItemTypes.ITEM, itemSource,
    function(connect, monitor) {
        return {
            connectDragSource: connect.dragSource(),
            isDragging: monitor.isDragging()
        };
});
var DropTargetDecorator = ReactDnD.DropTarget(ItemTypes.ITEM, itemTarget,
    function(connect, monitor) {
        return {
            connectDropTarget: connect.dropTarget(),
        };
});
module.exports = DropTargetDecorator(DragSourceDecorator(Item));

似乎嵌套项没有正确传递dragSource和dropTarget,因为当我在React DevTools浏览器扩展中查看结构时,它们没有被包装,但我不确定,因为我觉得React dnd应该处理好这一点。提前感谢,如有任何指导,我们将不胜感激。

您不必将"connectDropTarget"answers"connectDropTarget"传递到嵌套项。相反,你必须包装内项目的渲染方法,而不是

   return (<Item
           key={item.id}...

写入

   return (<WrappedItem
          key={item.id}… 

而不是

   module.exports = DropTargetDecorator(DragSourceDecorator(Item));

写入

   var WrappedItem = DropTargetDecorator(DragSourceDecorator(Item));
   module.exports = WrappedItem;

当我试图在拖动结束前操作树时,也遇到了同样的问题。也就是说,在dragEnd()之前更改叶子的顺序会给我同样的错误。但简单列表非常有效。我在DnD期间停下来更改树(我们的设计师说——这不是好的UX:跳到鼠标指针子树)。所以我的解决方案是:在DnD期间,只更改目标和源子树的类(但不更改叶的顺序),比如:

  .source-dnd {
    opacity: 0.2;
  }
  .target-dnd-add {
    border: 1px dotted #aaa;
  }
  .target-dnd-after {
    border-bottom: 1px dotted #aaa;
  }
  .target-dnd-before {
    border-top: 1px dotted #aaa;
  }

也不要忘记monitor.isOver({ shallow: true })