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

