React组件注册事件监听器时,useRef替代useState的原因咨询
问题代码
const movingRef = useRef(false); const handleStartMove = (e) => { movingRef.current = true; e.stopPropagation(); // handleMove doesn't take the latest value if we use a state instead of a ref document.addEventListener('mousemove', handleMove); if (onMoveStart) { onMoveStart(e, movingRef.current, angle); } }; const handleMove = (e) => { e.stopPropagation(); e.preventDefault(); if (!movingRef.current) { return; } this.moveDOMElement(e); };
问题描述
将上述代码中的movingRef = useRef(false)替换为const [moving, setMoving] = useState(false)后,代码无法正常运行;而使用useRef则可正常工作。我了解到useRef的变更传播更快,因为回调函数handleMove会获取useState变量moving的缓存值而非最新值,而useRef总能获取最新值。请问情况是否如此?
我听说这是闭包相关的行为,使用state变量会引发同步和副作用问题,而useRef可避免此类问题,达到预期效果。
回答
你的理解核心是对的,但“变更传播更快”这个说法不准确,本质原因是闭包捕获变量的特性:
用
useState定义moving时,组件每次重新渲染都会生成新的moving变量和新的handleMove函数。但handleStartMove中添加的mousemove监听,绑定的是当前渲染周期的handleMove——这个函数捕获的是当前周期的moving值(初始为false)。后续调用setMoving(true)会触发组件重新渲染,生成新的moving和新的handleMove,但旧的handleMove依然被mousemove监听引用着,它里面的moving永远是最初的false,导致handleMove里的判断永远不通过,逻辑失效。而
useRef的current属性是一个可变的容器,它不会随着组件重新渲染而创建新的变量,所有渲染周期的函数都共享同一个current引用。不管handleMove是哪个渲染周期创建的,它访问的都是同一个movingRef.current,总能拿到最新的值,所以逻辑可以正常执行。
总结来说:useState的变量是“快照式”的,每个渲染周期的变量都是独立的;useRef的current是“引用式”的,始终指向同一个内存地址,能获取最新值。
内容的提问来源于stack exchange,提问作者user11092881

