拖拽后React与纯HTML的onMouseMove事件表现差异问题
React拖拽元素后父元素onMouseMove事件不触发的问题解决
将红色div(子元素)拖拽到蓝色div(父元素)后,鼠标悬停在红色div上时,React中蓝色div的onMouseMove事件不会触发,但相同逻辑在纯HTML中可以正常触发。
纯HTML示例代码
<div> <div id="child" draggable="true" ondragstart="drag(event)" style="background-color: red; width: 200px; height: 200px;" > 1. Put me on blue container<br/> 2. Check console<br/> 3. Move mouse over containers </div> <div id="newParent" ondrop="drop(event)" onmousemove="mouseMove(event)" ondragover="allowDrop(event)" style="background-color: blue; width: 400px; height: 400px;" ></div> </div> <script> function drag(ev) { ev.dataTransfer.setData('id', ev.target.id); } function drop(ev) { ev.preventDefault(); var data = ev.dataTransfer.getData('id'); var element = document.getElementById(data); ev.target.appendChild(element); } function allowDrop(ev) { ev.preventDefault(); } function mouseMove(event) { console.clear(); console.log('onMouseMoveDiagram', event.clientX, event.clientY); } </script>
React示例代码
export function App(props) { return ( <div> <div id="child" draggable onDragStart={(ev) => { ev.dataTransfer.setData('id', ev.currentTarget.id); }} style={{ backgroundColor: 'red', width: '200px', height: '200px' }} > 1. Put me on blue container <br /> 2. Check console <br /> 3. Move mouse over containers </div> <div id="newParent" onDrop={(ev) => { ev.preventDefault(); const data = ev.dataTransfer.getData('id'); const element = document.getElementById(data); ev.currentTarget.appendChild(element); }} onMouseMove={(event) => { console.clear(); console.log('onMouseMoveDiagram', event.clientX, event.clientY); }} onDragOver={(ev) => ev.preventDefault()} style={{ backgroundColor: 'blue', width: '400px', height: '400px' }} ></div> </div> ); }
问题原因
核心冲突在于React的合成事件机制与原生DOM操作的不兼容:
- React的所有合成事件(比如
onMouseMove)并非直接绑定在目标元素上,而是委托到根节点统一处理。 - 当你通过原生
appendChild方法将红色div添加到蓝色div后,这个红色div脱离了React的DOM管理范围。鼠标在它上面移动时,原生事件会被它捕获,但无法通过React的事件委托机制冒泡到父元素的合成事件回调中。 - 纯HTML中事件直接绑定在元素上,原生事件的冒泡机制不受限制,因此父元素的
onmousemove能正常触发。
解决方案
方案1:用React状态管理元素归属(推荐)
遵循React的数据流原则,通过状态控制子元素的父容器,让React负责DOM渲染和事件绑定:
import { useState } from 'react'; export function App(props) { const [childContainer, setChildContainer] = useState('default'); const handleDragStart = (ev) => { ev.dataTransfer.setData('id', ev.currentTarget.id); }; const handleDrop = () => { setChildContainer('newParent'); }; const handleMouseMove = (event) => { console.clear(); console.log('onMouseMoveDiagram', event.clientX, event.clientY); }; return ( <div> {childContainer === 'default' && ( <div id="child" draggable onDragStart={handleDragStart} style={{ backgroundColor: 'red', width: '200px', height: '200px' }} > 1. Put me on blue container <br /> 2. Check console <br /> 3. Move mouse over containers </div> )} <div id="newParent" onDrop={handleDrop} onMouseMove={handleMouseMove} onDragOver={(ev) => ev.preventDefault()} style={{ backgroundColor: 'blue', width: '400px', height: '400px' }} > {childContainer === 'newParent' && ( <div id="child" draggable onDragStart={handleDragStart} style={{ backgroundColor: 'red', width: '200px', height: '200px' }} > 1. Put me on blue container <br /> 2. Check console <br /> 3. Move mouse over containers </div> )} </div> </div> ); }
方案2:手动给子元素添加原生事件监听
如果必须直接操作DOM,可以在拖拽完成后,给红色div添加原生mousemove事件监听,手动触发父元素的事件处理函数:
import { useRef } from 'react'; export function App(props) { const parentRef = useRef(null); const handleMouseMove = (event) => { console.clear(); console.log('onMouseMoveDiagram', event.clientX, event.clientY); }; const handleDrop = (ev) => { ev.preventDefault(); const data = ev.dataTransfer.getData('id'); const element = document.getElementById(data); parentRef.current.appendChild(element); // 给子元素绑定原生事件,手动调用父元素的处理函数 element.addEventListener('mousemove', handleMouseMove); }; return ( <div> <div id="child" draggable onDragStart={(ev) => { ev.dataTransfer.setData('id', ev.currentTarget.id); }} style={{ backgroundColor: 'red', width: '200px', height: '200px' }} > 1. Put me on blue container <br /> 2. Check console <br /> 3. Move mouse over containers </div> <div ref={parentRef} id="newParent" onDrop={handleDrop} onMouseMove={handleMouseMove} onDragOver={(ev) => ev.preventDefault()} style={{ backgroundColor: 'blue', width: '400px', height: '400px' }} ></div> </div> ); }
内容的提问来源于stack exchange,提问作者Juciano Victor
相关产品推荐
相关产品推荐

