面试 3 连问:
- setState 是同步还是异步?
- 为什么连续 setState 只渲染一次?
- 怎样在 setState 后拿到最新 DOM?
读完这篇,你可以把答案倒背如流。
一、setState 到底是什么?
一句话:
setState 是 React 用来「排队」更新组件状态的函数,最终触发一次批量渲染。
它有两种形态:
| 形态 | 语法 | 场景 |
|---|---|---|
| 对象式 | setState({count: 1}) | 不关心旧状态 |
| 函数式 | setState(prev => ({count: prev.count + 1})) | 依赖旧状态 |
二、同步还是异步?看「调用环境」!
React 18 以前:
| 调用位置 | 表现 | 示例 |
|---|---|---|
| React 事件 | 异步批量 | onClick={() => { setState({a: 1}); console.log(this.state.a) }} // 仍是旧值 |
| 原生事件 / setTimeout / Promise | 同步 | setTimeout(() => setState({a: 1}), 0) // 立即更新 |
React 18 默认全部走异步批量(createRoot),除非 flushSync。
三、批量更新原理:Transaction + 队列
// 伪代码
enqueueSetState(state) {
this.pendingQueue.push(state);
if (!this.isBatching) {
this.flush();
}
}
- 事件回调里 React 开启一个 transaction,所有 setState 先排队。
- 事务结束 → 一次性合并 → 触发一次 render → 减少重渲染。
四、连续 setState 的正确姿势
// ❌ 错误:只加 1,因为三次合并都是基于同一个 baseState
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
// ✅ 正确:函数式让每次拿到最新 prevState
this.setState(prev => ({ count: prev.count + 1 }));
this.setState(prev => ({ count: prev.count + 1 }));
this.setState(prev => ({ count: prev.count + 1 }));
// 最终 count = +3
五、setState 回调 & flushSync:拿最新 DOM
// 1️⃣ 回调用法(类组件)
this.setState({ text: 'hello' }, () => {
console.log('DOM 已更新', this.ref.current);
});
// 2️⃣ flushSync(函数组件)
import { flushSync } from 'react-dom';
flushSync(() => setValue(100));
console.log(inputRef.current.value); // 100
六、函数组件里的 useState 与 setState 异同
| 特性 | useState | setState |
|---|---|---|
| 批量更新 | ✅ | ✅ |
| 回调 | ❌(用 useEffect 代替) | ✅ |
| 函数式更新 | setCount(c => c + 1) | 同左 |
| 合并 | 直接替换 | 浅合并 |
七、一次完整生命周期穿行
class Demo extends React.Component {
state = { count: 0 };
handleClick = () => {
this.setState({ count: this.state.count + 1 });
console.log('同步 log:', this.state.count); // 旧值
};
render() {
console.log('render with count:', this.state.count);
return <button onClick={this.handleClick}>{this.state.count}</button>;
}
}
输出顺序:
同步 log: 0 → render with count: 1
八、高频面试问答速记卡
| 问题 | 一句话答案 |
|---|---|
| setState 后立即能拿到最新 state 吗? | 不能,除非在 flushSync 或回调里。 |
| 为什么推荐函数式 setState? | 保证基于最新状态计算。 |
| React 18 之后 setState 都是异步吗? | 默认异步,flushSync 强制同步。 |
九、总结
- setState 本质是“排队调度”,不是立即写 DOM。
- 函数式更新 = 状态安全。
- 想拿最新 DOM → 用回调 / flushSync。
背下这张卡片,下次面试官再问 setState,你可以 反问他:想听同步还是异步场景?