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

React组件鼠标事件处理函数中useState无法正常生效该如何解决?

问题根因

你遇到的是React的闭包陷阱问题:

  • 你的useEffect依赖是空数组,只会在组件首次渲染时执行一次,因此你绑定到document上的mouseMove、mouseUp函数,永远是首次渲染时生成的版本
  • 首次渲染时dragging的初始值是false,这个初始值被封存在旧的事件处理函数闭包里,后续你调用setDragging更新状态只会触发组件重渲染、生成新的mouseMove函数,但你没有重新绑定事件,所以触发的永远是存了旧值的旧函数

解决方案

推荐两种常用的修复方式:

方案1:给useEffect添加依赖,自动更新事件绑定

把事件处理函数依赖的dragging、offset加入useEffect依赖数组,每次依赖变化时会自动清理旧的事件监听、绑定新的处理函数,就能拿到最新的状态值:

useEffect(() => {
  document.addEventListener('mouseup', mouseUp);
  document.addEventListener('mousemove', mouseMove);
  return () => {
    document.removeEventListener('mouseup', mouseUp);
    document.removeEventListener('mousemove', mouseMove);
  };
// 新增状态依赖
}, [dragging, offset]);

方案2:用useRef存储状态,规避闭包问题

useRef的current属性是可变引用,不会被闭包捕获固定值,随时读取都能拿到最新值,修改也不会触发重渲染,更适合拖拽这类高频交互场景:

import React, { useEffect, useRef } from 'react';

interface Coordinate {
  x: number;
  y: number;
}

const SimpleDraggableWrapper = (props: any) => {
  const element = useRef<HTMLDivElement>(null);
  // 用ref存储可变状态
  const offsetRef = useRef<Coordinate | null>(null);
  const draggingRef = useRef<boolean>(false);

  const mouseDown = (event: React.MouseEvent) => {
    if (element.current) {
      offsetRef.current = {
        x: event.clientX - element.current.offsetLeft,
        y: event.clientY - element.current.offsetTop,
      };
      draggingRef.current = true;
      console.log('Dragging set to true');
    }
  };

  const mouseUp = (event: MouseEvent) => {
    draggingRef.current = false;
    console.log('Dragging set to false');
  };

  const mouseMove = (event: MouseEvent) => {
    console.log(draggingRef.current);
    if (draggingRef.current && element.current && offsetRef.current) {
      element.current.style.left = `${event.clientX - offsetRef.current.x}px`;
      element.current.style.top = `${event.clientY - offsetRef.current.y}px`;
    }
  };

  useEffect(() => {
    document.addEventListener('mouseup', mouseUp);
    document.addEventListener('mousemove', mouseMove);
    return () => {
      document.removeEventListener('mouseup', mouseUp);
      document.removeEventListener('mousemove', mouseMove);
    };
  }, []);

  return (
    <div ref={element} onMouseDown={mouseDown} style={{ position: 'absolute' }}>
      {React.cloneElement(props.children)}
    </div>
  );
};

export default SimpleDraggableWrapper;

额外提示:记得给可拖拽的外层div添加position: absolute/position: fixed样式,否则修改left/top属性不会生效。

内容的提问来源于stack exchange,提问作者bassman21

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 08:45:03