【问题标题】:React JS Events Not Firing for the Last rendered elementReact JS 事件未针对最后呈现的元素触发
【发布时间】:2014-08-23 14:11:48
【问题描述】:

我创建了一个拖放界面,最终将变成一个表单构建器。我创建了表单构建器http://jsfiddle.net/szASZ/1/ 的小提琴。当您将顶部项目之一拖到其下方的灰色拖放区时,一切似乎都正常工作。

但是,我遇到的问题是当您刷新页面/小提琴并尝试对放置区域内的项目进行排序时。如果您抓住第一个放置区顶部的“无线电输入”并将其移入或移出该放置区,则一切正常。

现在,尝试将最后一项“复选框输入”拖到其放置区域中,一切都应该正常工作。最后,刷新页面/小提琴并将最后一个“复选框输入”移动到另一个拖放区。占位符将显示,然后永远不会消失。我发现在拖放周期的这个实例中从未调用过“onDragEnd”事件,而是每隔一次调用一次。此事件通常会中继到表单构建器的顶层,在该构建器中它获取被拖动的项目并将其设置到新的数据数组中。

为了让事情变得更有趣,如果我将一个空对象添加到包含已删除项目的数组中(http://jsfiddle.net/szASZ/ - 第 30、34 行),一切都会按预期工作,因此看起来“放置区”项目数组未正确触发“onDragEnd”事件。

componentWillMount: function () {
    var newInput = [
        { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Text Input" },
        { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Checkbox Input" },
        { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Radio Input" }
    ];

    var newZones = [
        [
            { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Radio Input" },
            { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Text Input" },
            { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Checkbox Input" }
            ,{}
        ],
        [
            { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Text Input" },
            { "classes": "soft-half push-half--bottom brand-bg--gray-dark brand-color--white", "title": "Checkbox Input" }
            ,{}
        ]
    ];

    this.setState({ inputs: newInput });
    this.setState({ zones: newZones });
}

基本上,这是我遇到的主要问题,我不知道为什么其他所有项目都被排序、删除并且事件正确触发,但最后一个?谢谢!

【问题讨论】:

  • 感谢您的详细描述和复制案例。

标签: javascript drag-and-drop reactjs


【解决方案1】:

这与我几个月前提交的一个 React 错误直接相关/导致:

#1355: touchmove doesn't fire on removed element

React 利用事件冒泡并将所有事件绑定到文档根目录,这在大多数情况下都有效,但在这种特殊情况下会失败。与鼠标事件不同,触摸和拖动事件总是发送到接收到touchstartdragstart 事件的事件(而不是指针当前所在的元素)。如果接收到 dragstart 事件的元素从 DOM 中删除(在这种特殊情况下,因为它从它开始的列表中消失了),那么(分离的)元素仍将收到 dragend 事件,但它赢了'不要冒泡到文档根目录,所以 React 永远不会看到该事件。

您仅在拖动最后一个元素时看到此内容,因为您未正确使用 key 属性。因为您当前使用索引作为键,所以当您有一个列表 ABCD 并开始拖动 B(导致列表变为 ACD)时,React 将第二个元素(以前的 B)变异为具有文本“C”,变异第三个元素元素(以前是 C)具有文本“D”,并删除第四个元素(以前是 D)。如果您使用唯一的每个项目 ID 作为键而不是索引,React 将始终删除您正在拖动的元素,这将更容易推理。 (由于该错误,您会看到这种行为总是发生,而不是仅在拖动最后一个元素时发生。)

作为一种解决方法,您可以在 dragstart 处理程序中手动绑定dragend/dragenter/dragleave 处理程序,并在dragend 上清理它们。对于这些事件,这将完全避开 React 的事件系统:

// DRAGGING
dragStart: function (e) {
    this.getDOMNode().addEventListener('dragend', this.dragEnd, false);
    this.getDOMNode().addEventListener('dragenter', this.dragEnter, false);
    this.getDOMNode().addEventListener('dragleave', this.dragLeave, false);

    e.dataTransfer.effectAllowed = 'move';
    e.dataTransfer.setData('text/html', e.target.innerHTML);

    this.props.dragStart(this.props.item, { sorting: true });
},
dragEnd: function (e) {
    this.getDOMNode().removeEventListener('dragend', this.dragEnd, false);
    this.getDOMNode().removeEventListener('dragenter', this.dragEnter, false);
    this.getDOMNode().removeEventListener('dragleave', this.dragLeave, false);

    var opts = e.dataTransfer.dropEffect === 'none' ? { success: false } : { success: true };

    this.props.dragEnd(opts);
},

使用该设置,您只需在 render 中指定 onDragStart

(我会看看能不能解决这个问题——据我所知,除了我之外,你是第一个遇到这个问题的人,所以这不是我们的优先事项。)

【讨论】:

  • 我也遇到了这个问题。感谢您的解决方案!
【解决方案2】:

我有一个类似的错误,onDragEnd 没有为重新渲染的节点触发。

@Ben Alpert,感谢您的解决方法,太棒了!我建议稍微修改一下:

dragEnd: function(e) {

  // component can be umounted and getDOMNode will throw in that case
  if (this.isMounted()) {
    this.getDOMNode().removeEventListener('dragend', this.dragEnd, false);
    // ...
  }
  // ... invoke the callback
},

【讨论】:

    【解决方案3】:

    我遇到了类似的问题,并将其转换为功能组件。

    function DragCell () {
        const [ DragTarget, SetDragTarget ] = React.useState<HTMLSpanElement>();
    
        // Manually bind the event, because if the component un-renders, we still want to fire the event
        React.useEffect(() => {
            if (!DragTarget) {
                return;
            }
    
            const onDragEnd = () => SetDragTarget(undefined);
            DragTarget.addEventListener('dragend', onDragEnd);
            return () => {
                // In the event the component is unloaded, we still want to call the clean up function
                // Put your code you want to run on dragEnd here
                DragTarget.removeEventListener('dragend', onDragEnd);
            };
        }, [DragTarget]);
    
        return (
            <td data-isdragging={ !!DragTarget } className='DragCell'>
                <span
                    className='DragHandle'
                    draggable
                    onDragStart={event => {
                        event.dataTransfer.setData('dragging', '');
                        SetDragTarget(event.currentTarget);
                    }}
                >Drag Handle</span>
            </td>
        );
    };
    

    【讨论】:

      猜你喜欢
      • 2019-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-28
      • 2023-04-06
      • 2019-07-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多