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

状态更新函数中块级作用域变量值异常问题排查

问题:状态更新结果与预期不符的原因分析

预期结果

  • 浏览器页面显示:
// 1/2/3 (每个数字间隔0.5秒出现)
  • 控制台输出:
/*
  null,1 
  1,2
  2,3
*/

实际结果

  • 浏览器页面显示:
// 0/2/3 
  • 控制台输出:
/*
    null,0 
    0,2 
    2,3
*/

App.js

const { useEffect, useState } = React;

function App() {
  const [counter, setCounter] = useState(null);

  useEffect(() => {
    let i = 0;
    setTimeout(() => {
      setCounter((c) => {
        console.log(c, i);
        return i;
      });
      i = i + 1;
      setTimeout(() => {
        setCounter((c) => {
          console.log(c, i);
          return i;
        });
        i = i + 1;
        setTimeout(() => {
          setCounter((c) => {
            console.log(c, i);
            return i;
          });
          i = i + 1;
        }, 500);
      }, 500);
    }, 500);
  }, []);
  return <> {counter && counter} </>;
}

ReactDOM.createRoot(document.body).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js"></script>

更新:2月3日

  • a) 从Nick的回答中,我最大的收获是纯函数的必要性——这是必须遵守的核心准则。
  • b) 状态设置器是同步还是异步调用,取决于React运行时的决策,这点完全合理。但像setTimeout这类异步函数里,有部分代码是同步执行的。我们知道JS运行时的机制:一旦遇到setTimeout,就会交由浏览器的Web API处理,延迟指定时间后,浏览器会把回调函数推入任务队列,事件循环再取出执行完成。目前我还没搞懂的是,React是如何介入这个流程,把状态更新变成同步调用的。(这不是提问,我还在自己琢磨)
  • c) 有时候需要重新审视纯函数的定义。因为这里的状态更新函数签名固定是(oldState)=>newState,没办法额外传参,这就限制了我们从外部上下文传值。要解决这个问题,可以在作用域里创建新变量,确保它的值不会被意外修改。

下面这段代码里的更新器虽然还是引用了外部变量,但依然是纯函数。这个话题在React的相关规则里专门讨论过,贴出来供参考和评论:

...
  useEffect(() => {
    let i = 0;
    setTimeout(() => {
      let newI = i;
      setCounter((c) => {
        console.log(c, newI);
        return newI;
      });
      i = i + 1;
      setTimeout(() => {
        let newI = i;
        setCounter((c) => {
          console.log(c, newI);
          return newI;
        });
        i = i + 1;
        setTimeout(() => {
          let newI = i;
          setCounter((c) => {
            console.log(c, newI);
            return newI;
          });
          i = i + 1;
        }, 500);
      }, 500);
    }, 500);
  }, []);
  return <> {counter && counter} </>;
}
...

内容的提问来源于stack exchange,提问作者WeDoTheBest4You

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 17:26:01