React中createRoot与ReactDOM.render的setState同步异步差异原因
React中createRoot与ReactDOM.render的setState同步/异步行为差异解析
核心原因是React 18引入createRoot时,彻底重构了状态更新的批处理策略,和旧版ReactDOM.render的规则完全不同。
先看ReactDOM.render(React 17及旧模式)的输出逻辑:
你的代码里,componentDidMount属于React的生命周期回调,处于React的同步更新上下文:
- 前两次
setState会被React合并批处理,不会立即更新state,所以连续两次console.log(this.state.count)输出的都是初始值0。 - 批处理执行完成后,state会被更新为
1(两次setState都是基于初始0累加,合并后只生效一次+1)。 - 而
setTimeout属于浏览器宏任务,脱离了React的更新上下文,这里的setState会同步执行更新:- 第一次
setState把1变成2,立即更新state,所以console.log输出2; - 第二次
setState把2变成3,同样立即更新,console.log输出3。
最终输出就是0,0,2,3。
- 第一次
再看createRoot(React 18新模式)的输出逻辑:
React 18默认开启了自动批处理(Automatic Batching),不管代码是在生命周期、合成事件,还是setTimeout、Promise这类异步回调里,所有setState都会被自动批处理:
- 前两次
componentDidMount里的setState依然被批处理,console.log输出初始值0,批处理后state更新为1。 - 关键差异在
setTimeout里的两次setState:它们也被React自动批处理了,不会立即更新state。所以两次setState都是基于当前的1来计算,但在批处理执行前,state不会变化,两次console.log输出的都是1。等批处理完成后,state才会被更新为2,但这时候console.log已经执行完了。
最终输出就是0,0,1,1。
额外补充:
如果在React 18里想取消某段代码的批处理,可以用ReactDOM.flushSync()包裹setState,这样就能回到旧模式的同步更新行为。
内容的提问来源于stack exchange,提问作者ChenLee
相关产品推荐
相关产品推荐

