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

React Canvas绘图中useState绑定鼠标按下事件失效原因探究

React Canvas绘图中useState替代useRef导致绘图失效的原因分析

我正在使用React与Canvas开发绘图应用,然而当将isMouseDown变量通过useState而非useRef定义时,画布无法正常绘图。我知道不应该使用useState(会导致过多重渲染),但特别想了解使用useState时绘图功能失效的具体原因。以下是对应的组件代码:

function App() {
  const [isMouseDown, setIsMouseDown] = useState(false);
  const isMouseDownRef = useRef(false);
  const canvasRef = useRef(null);
  const ctx = useRef(null);

  function triggerMouseDown() {
    setIsMouseDown(true);
    //isMouseDownRef.current = true;
  }

  function triggerMouseUp() {
    setIsMouseDown(false);
    //isMouseDownRef.current = false;
  }

  useEffect(() => {
    if(canvasRef.current) {
      ctx.current = canvasRef.current.getContext("2d");
      canvasRef.current.width = 720;
      canvasRef.current.height = 480;
      canvasRef.current.addEventListener("mousemove", (e) => {
        draw(
          e.clientX - canvasRef.current.getBoundingClientRect().left, 
          e.clientY - canvasRef.current.getBoundingClientRect().top
        )
      })

      canvasRef.current.addEventListener("mousedown", triggerMouseDown)

      window.addEventListener("mouseup", triggerMouseUp)
    }

    return () => {
      canvasRef.current.removeEventListener("mousedown", triggerMouseDown);
      window.removeEventListener("mouseup", triggerMouseUp)
    }
  }, [])

  function draw(x, y) {
    if(isMouseDown) {
      ctx.current.beginPath();
      ctx.current.fillStyle = "blue";
      ctx.current.arc(x, y, 20, 0, 2 * Math.PI);
      ctx.current.stroke();
    }
  }
  
  return (
    <div className="App">
      <canvas id="canvas1" ref={canvasRef}></canvas>
      <h1>{JSON.stringify(isMouseDown)}</h1>
    </div>
  );
}

失效的具体原因

  • 闭包捕获的状态始终是初始值:你的useEffect依赖项为空数组,只在组件首次挂载时执行一次。此时绑定的mousemove事件监听器,会捕获第一次渲染时的draw函数——这个函数里的isMouseDown是初始的false。
  • useState更新不影响旧闭包:当调用setIsMouseDown更新状态时,React会触发组件重渲染,生成新的isMouseDown变量和新的draw函数。但之前绑定的mousemove监听器仍然引用旧的draw函数,它里面的isMouseDown永远是false,绘图逻辑自然不会执行。
  • 页面h1能正确显示的原因:每次重渲染都会生成新的JSX,其中的{JSON.stringify(isMouseDown)}使用的是当前最新的状态值,但事件监听器里的函数是首次挂载时绑定的旧版本,不受后续重渲染影响。

对比useRef的工作方式:useRef的current属性是一个可变的容器,在组件整个生命周期中保持同一个引用。不管组件怎么重渲染,事件监听器里的draw函数都能访问到最新的isMouseDownRef.current值,所以绘图功能正常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 20:35:24