useState与useRef+useState在watchHeadingAsync中的行为差异排查
问题:React状态更新中闭包引发的异常行为
我原本认为以下两段代码表现应该一致,但实际结果不同。需求是用watchHeadingAsync更新罗盘航向,实现从旧航向到新航向的动画过渡。
第一段代码里,heading[0]始终是0,只有heading[1]能正确显示当前航向;第二段代码则符合预期。这是不是存在没注意到的竞态条件?
第一段代码(异常表现)
const [heading, setHeading] = useState([0,0]); useEffect(() => { Location.watchHeadingAsync((headObj) => { let h = Math.round(headObj.magHeading) * -1; setHeading([heading[1], h]); }); }, []); console.log(heading[0], '->', heading[1]);
第二段代码(符合预期)
const [heading, setHeading] = useState([0,0]); const oldHeading = useRef(0); const newHeading = useRef(0); useEffect(() => { Location.watchHeadingAsync((headObj) => { let h = Math.round(headObj.magHeading) * -1; oldHeading.current = newHeading.current; newHeading.current = h; setHeading([oldHeading.current, newHeading.current]); }); }, []); console.log(heading[0], '->', heading[1]);
原因分析
第一段代码的问题出在闭包捕获的state快照:
useEffect的依赖数组是空的,意味着它只在组件挂载时执行一次,内部的回调函数会捕获初始的heading状态(也就是[0,0])。- 后续每次
watchHeadingAsync触发回调时,引用的heading始终是挂载时的初始值,所以heading[1]永远是0,导致setHeading([0, h]),最终heading[0]一直保持为0。
第二段代码用useRef解决了这个问题:
useRef的.current属性是可变的,且不会被React的状态更新机制捕获快照,它始终指向最新的值。- 每次回调触发时,都会先把
newHeading.current(上一次的航向)赋值给oldHeading.current,再更新newHeading.current为新航向,最后用这两个最新值更新state,自然就能得到正确的旧航向和新航向。
内容的提问来源于stack exchange,提问作者akremer
相关产品推荐
相关产品推荐

