⚡ 30 秒速记
- 核心判断:状态更新是否立刻可见取决于调度边界和批处理,不应简单记成“同步”或“异步”
- 原理主线:围绕 「从一道面试题说起」、「异步的动机和原理——批量更新的艺术」、「“同步现象”背后的故事:从源码角度看 s」 建立输入、状态变化与输出之间的因果关系
- 文章范围:解析了 React setState 的同步与异步机制,通过代码示例和原理分析,帮助开发者理解 setState 在不同场景下的执行时机、批量更新机制及其对组件状态管理的影响,是面试和实际开发中常见高频问题的权威解答。
- 边界与代价:
React 18自动批处理范围扩大,读取旧值、连续更新和强制同步提交需要分别讨论 - 工程落地:依赖前值时使用函数式更新;只有确有 DOM 读取时才考虑同步提交,并评估阻塞代价
setState 不能简单归类为同步或异步,状态何时可见取决于 React 的批处理边界。 在受 React 管控的事件中,多次更新会先进入队列并合并,避免每次调用都触发一次完整的重渲染,所以紧接着读取往往还是旧值。对象形式连续写入同一字段时,读取的又是同一个旧状态,最终不等于累加多次。原文中 setTimeout 后立即可见属于 React 15 的历史实现现象,不能直接套到其他版本。
这篇文章不要按 API 清单来背。先用上面的 Mind Map 建立全局结构,再通过交互 DEMO 观察正常路径和边界路径如何改变状态;阅读正文时重点核对每一步的输入、负责执行的参与者、产生的中间状态以及最终可观察结果。遇到版本敏感结论,要把“历史实现”“当前行为”和“工程兼容策略”分开说明;遇到性能或架构取舍,则用实际指标、失败现象和验证手段支撑判断。
版本校准: 原理文章中的代码代表特定实现与写作时间。应用到当前项目时,应先确认浏览器、框架或工具的主版本,再区分稳定的规范语义、可变化的内部实现和项目自身约束。
# 从一道面试题说起
这是一道变体繁多的面试题,在 BAT 等一线大厂的面试中考察频率非常高。首先题目会给出一个这样的 App 组件,在它的内部会有如下代码所示的几个不同的 setState 操作:
import React from "react";
import "./styles.css";
export default class App extends React.Component{
state = {
count: 0
}
increment = () => {
console.log('increment setState前的count', this.state.count)
this.setState({
count: this.state.count + 1
});
console.log('increment setState后的count', this.state.count)
}
triple = () => {
console.log('triple setState前的count', this.state.count)
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
console.log('triple setState后的count', this.state.count)
}
reduce = () => {
setTimeout(() => {
console.log('reduce setState前的count', this.state.count)
this.setState({
count: this.state.count - 1
});
console.log('reduce setState后的count', this.state.count)
},0);
}
render(){
return <div>
<button onClick={this.increment}>点我增加</button>
<button onClick={this.triple}>点我增加三倍</button>
<button onClick={this.reduce}>点我减少</button>
</div>
}
}