React JS 事件未针对最后呈现的元素触发

Posted

技术标签:

【中文标题】React JS 事件未针对最后呈现的元素触发【英文标题】:React JS Events Not Firing for the Last rendered element 【发布时间】: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 );

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

【问题讨论】:

感谢您的详细描述和复制案例。 【参考方案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>
    );
;

【讨论】:

以上是关于React JS 事件未针对最后呈现的元素触发的主要内容,如果未能解决你的问题,请参考以下文章

角度单击事件处理程序未触发更改检测

AngularJS模板中的元素仍未触发jQuery委托事件

Firebase 函数 Google Analytics 触发器未针对 Web 事件触发

影子 DOM 中的 React 组件时单击事件未触发

PushSharp 事件未针对 WP8 触发

检查 onclick 事件触发的表单元素