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
相关产品推荐
相关产品推荐

