React中移除document的EventListener问题:拖拽时mouseup事件异常
问题解决:React中可拖拽div的事件监听移除失败与无限循环问题
核心错误分析
- 事件移除函数引用错误:
onMouseUpLeftResize里移除mousemove事件时,错误传入了自身函数,而实际绑定的是onMouseMoveLeftResize,导致无法正确解除监听。 - 清理逻辑颠倒:useEffect的返回清理函数中,错误给
storedResizerLine添加了mousedown事件,正确操作应该是移除。 - 全局事件未完整清理:组件卸载或effect重新执行时,未移除document上的
mousemove和mouseup事件,残留监听会引发异常和循环。
修复后的完整代码
import { useEffect, useRef } from 'react'; // 假设CANVAS_WIDTH为已定义常量 const CANVAS_WIDTH = 800; const ResizableDiv = () => { const resizableline = useRef(null); const resizableArea = useRef(null); const startX = useRef(0); // 用ref存储初始点击位置,避免不必要重渲染 useEffect(() => { const storedResizerLine = resizableline.current; if (!storedResizerLine || !resizableArea.current) return; const onMouseDownLeftResize = (e) => { startX.current = e.clientX; document.addEventListener("mousemove", onMouseMoveLeftResize); document.addEventListener("mouseup", onMouseUpLeftResize); }; const onMouseMoveLeftResize = (event) => { const dx = event.clientX - startX.current + 40; resizableArea.current.style.width = `${dx}px`; }; const onMouseUpLeftResize = () => { // 移除正确的事件函数引用 document.removeEventListener("mousemove", onMouseMoveLeftResize); document.removeEventListener("mouseup", onMouseUpLeftResize); // 保留原有的计算逻辑 const resizableAreaWidth = window.getComputedStyle(resizableArea.current).width; const imageWidth = window.innerWidth - parseInt(resizableAreaWidth); const percentageIncrement = ((parseInt(imageWidth) - parseInt(CANVAS_WIDTH)) * 100) / parseInt(CANVAS_WIDTH); }; storedResizerLine.addEventListener("mousedown", onMouseDownLeftResize); // 正确的清理逻辑:移除所有绑定的事件 return () => { storedResizerLine.removeEventListener("mousedown", onMouseDownLeftResize); document.removeEventListener("mousemove", onMouseMoveLeftResize); document.removeEventListener("mouseup", onMouseUpLeftResize); }; }, []); // ref的current变化不会触发effect,空依赖即可;需响应外部变量可添加到数组中 return ( <div style={{ display: 'flex', width: '100%', height: '300px' }}> <div ref={resizableArea} style={{ width: '200px', backgroundColor: '#eee' }}> 可调整区域 </div> <div ref={resizableline} style={{ width: '4px', backgroundColor: '#000', cursor: 'col-resize' }} ></div> <div style={{ flex: 1, backgroundColor: '#ccc' }}> 固定区域 </div> </div> ); }; export default ResizableDiv;
额外优化说明
- 用ref存储初始位置:将点击起始位置存在ref中,避免状态更新触发不必要的组件重渲染,减少effect执行次数。
- 边界检查:effect开头增加ref的非空判断,避免null引用报错。
- 完整清理:卸载时移除所有绑定的事件,彻底避免残留监听引发的问题。
内容的提问来源于stack exchange,提问作者Nidhi Sharma
相关产品推荐
相关产品推荐

