You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React中移除document的EventListener问题:拖拽时mouseup事件异常

问题解决:React中可拖拽div的事件监听移除失败与无限循环问题

核心错误分析

  1. 事件移除函数引用错误:onMouseUpLeftResize里移除mousemove事件时,错误传入了自身函数,而实际绑定的是onMouseMoveLeftResize,导致无法正确解除监听。
  2. 清理逻辑颠倒:useEffect的返回清理函数中,错误给storedResizerLine添加了mousedown事件,正确操作应该是移除。
  3. 全局事件未完整清理:组件卸载或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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 13:21:28