我创建了一个拖放界面,最终将其转换为表单生成器。我创建了表单构建器http://jsfiddle.net/szASZ/1/的小提琴。当您将其中一个顶部项目拖动到其下方的灰色拖放区域时,一切似乎都能正常工作。
但是,我遇到的问题是当您刷新页面/小提琴并尝试对放置区域内的项目进行排序时。如果你抓住"无线电输入"在第一个放置区域的顶部并将其移动到该放置区域内部或外部,一切正常。
现在,尝试拖动最后一项" Checkbox Input"在它的放置区域内,一切都应该正常工作。最后,刷新页面/小提琴并移动相同的,最后一个" Checkbox输入"到另一个下降区。占位符将显示,然后永远不会消失。我发现的是" onDragEnd"在拖放循环的这个实例期间永远不会调用event,但每隔一次调用它。此事件通常会传递到表单构建器的顶层,在该表单构建器中,它会拖动正在拖动的项目并将其设置在新的数据数组中。
为了让事情变得更有趣,如果我将一个空对象添加到包含已删除项目(http://jsfiddle.net/szASZ/ - 第30,34行)的数组上,一切都按预期工作,这样看起来就是最后一项在" drop zone"的数组中项目没有正确触发" 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 });
}
基本上,这是我遇到的主要问题,我不知道为什么每个其他项目都被排序,丢弃并且事件触发正确但最后一个?谢谢!
答案 0 :(得分:5)
这与我几个月前提交的React错误直接相关/引起:
#1355: touchmove doesn't fire on removed element
React利用事件冒泡并绑定文档根目录中的所有事件,这在大多数情况下都有效,但在这种特殊情况下会出现问题。与鼠标事件不同,触摸和拖动事件始终发送到接收touchstart
或dragstart
事件的事件(而不是指针当前所在的元素)。如果从DOM中删除了收到dragstart
事件的元素(因为它在此特定情况下从它开始的列表中消失),那么该(分离的)元素仍将接收dragend
事件,但它不会冒泡到文档根目录,所以React永远不会看到事件。
您只能在拖动最后一个元素时看到这一点,因为您未正确使用key
道具。因为您当前使用索引作为键,当您有一个列表ABCD并开始拖动B(导致列表成为ACD)时,React会改变第二个元素(以前为B)以使文本" C& #34;,将第三个元素(以前的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);
},
使用该设置,您只需在onDragStart
中指定render
。
(我知道我是否可以解决这个问题 - 据我所知,除了我以外,你是第一个打击它的人,所以它不是我们的优先考虑事项。 )
答案 1 :(得分: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
},

答案 2 :(得分:0)
我遇到了类似的问题并将其转换为功能组件。
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>
);
};