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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 16:25:10