React首次触发状态更新时updater函数执行顺序与文档不符问题咨询
import { useState } from 'react'; function App() { const [number, setNumber] = useState(0); function clickHandler() { setNumber((n) => { console.log('First updater function'); return n + 1; }); setNumber((n) => { console.log('Second updater function'); return n + 1; }); console.log('Other code in the click handler'); } return ( <> <h1>{number}</h1> <button onClick={clickHandler}>Increment number</button> </> ); } export default App;
传递给state setter的函数称为updater函数,React会将updater函数加入队列,在事件处理函数中所有其他代码执行完毕后再处理,在下一次渲染时遍历队列生成最终更新后的状态。
现象原因
这个首次点击执行顺序和文档描述不一致的情况,是React 18自动批处理机制的内置优化逻辑导致的,属于框架内部的实现细节,并不违背官方规则的核心约定。
首次点击的执行流程
- 触发eager求值优化
React针对从未触发过状态更新的组件做了专门的性能优化:第一次调用状态setter传入updater函数时,React会同步执行一次这个updater做预计算,快速判断本次更新是否真的会改变状态。如果计算得到的新值和当前状态完全一致,React会直接跳过后续的批处理、重渲染流程,减少不必要的性能开销。
首次点击时第一个setNumber调用触发了这个优化,同步执行第一个updater,所以首先输出First updater function。 - 第二个updater直接入队
第二个setNumber调用时,React已经检测到当前存在待处理的状态更新,不会再触发eager求值,直接把第二个updater加入更新队列,不会同步执行。 - 执行事件处理函数剩余代码
两个setter调用结束后,继续执行clickHandler剩下的同步代码,输出Other code in the click handler。 - 批处理阶段执行剩余updater
整个事件处理函数执行完毕后,React进入批处理阶段,遍历更新队列按顺序执行updater,此时执行第二个updater输出Second updater function。
后续点击符合预期的原因
当组件已经触发过至少一次状态更新后,React就不会再触发上述的eager求值优化,所有传入setter的updater都会先加入更新队列,等事件处理函数内所有同步代码执行完毕后,再统一按顺序执行队列里的updater,所以日志顺序就和官方文档描述完全一致。
这个优化只会影响updater函数的执行时机,不会改变最终的状态更新结果,每次点击后number最终都会增加2,逻辑结果完全符合预期。
内容的提问来源于stack exchange,提问作者Venkat
相关产品推荐
相关产品推荐

