状态更新函数中块级作用域变量值异常问题排查
问题:状态更新结果与预期不符的原因分析
预期结果
- 浏览器页面显示:
// 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
相关产品推荐
相关产品推荐

