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 利用事件冒泡并将所有事件绑定到文档根目录,这在大多数情况下都有效,但在这种特殊情况下会失败。与鼠标事件不同,触摸和拖动事件总是发送到接收到touchstart
或dragstart
事件的事件(而不是指针当前所在的元素)。如果接收到 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 事件未针对最后呈现的元素触发的主要内容,如果未能解决你的问题,请参考以下文章