🔴 1. setState
是异步的
在 React 中,我们通过 this.state
读取组件状态,通过 this.setState()
更新状态。但是,这个更新过程是"异步"的。
为什么异步?
调用 setState()
后,React 不会立即更新 this.state
,而是把更新操作放进一个队列,等到"合适时机"统一执行。这就是所谓的 批量更新,目的是提升性能,减少不必要的重复渲染。
例子:
js
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 这里打印的还是旧值
哪些情况是异步的?
-
异步(批量更新)生效:
- React 生命周期函数中(如
componentDidMount
) - React 合成事件中(如
onClick
)
- React 生命周期函数中(如
-
同步更新:
setTimeout
、setInterval
等原生异步回调中- 原生 DOM 事件中
如何获取更新后的值?
可以通过 setState
的 第二个参数 (回调函数)或 componentDidUpdate
钩子获取:
js
this.setState({ count: 1 }, () => {
console.log(this.state.count); // 1
});
componentDidUpdate() {
console.log(this.state.count);
}
🔴 2. setState
的批量更新机制
React 会在"批处理模式"中,把多个 setState
放入更新队列,最后统一处理,避免重复渲染。
批处理如何触发?
内部有个变量 isBatchingUpdates
控制是否开启批处理:
- React 生命周期、合成事件中:
isBatchingUpdates = true
,会批量更新 setTimeout
、原生事件中:isBatchingUpdates = false
,立即更新
更新过程简述:
- 调用
setState
时,状态被合并后放入更新队列 - 当前组件被标记为"脏组件",放入
dirtyComponents
- 等同步代码执行完后,React 再统一调用
render()
更新页面
🔴 3. 为什么不能直接修改 this.state
?
js
this.state.count = 2; // 错误的做法
- 直接修改不会进入 React 的更新队列,也不会触发组件重新渲染
- 还可能在后续合并队列时造成 状态丢失
正确做法是永远使用 setState
。
🔴 4. 批量更新是什么?
- 本质是 React 的性能优化手段
- 多次
setState
合并成一次更新,减少渲染次数 - 类似 Vue 的
nextTick
多次更新的例子:
js
this.setState({ a: 1 });
this.setState({ b: 2 });
React 会在下一轮更新中把 { a: 1, b: 2 }
合并起来更新,不会两次单独渲染。
🔴 5. setState 后发生了什么?
大致流程如下:
- 合并新状态
- 把组件标记为脏(dirty)
- 推入更新队列
- 在合适时机触发渲染
- 进行虚拟 DOM diff,判断哪些需要更新
- 最小化真实 DOM 操作
🔴 6. setState 的"陷阱" ------ 循环更新风险
在以下生命周期里 不要调用 setState
,否则会引起死循环:
shouldComponentUpdate
componentWillUpdate
因为这些函数会在每次更新前调用,而你里面又触发了新的 setState
,会无限循环。
🔴 7. React 的事务机制
React 通过事务机制(Transaction)控制更新的前后操作:
-
每次批量更新都封装在一个"事务"中
-
事务由多个 wrapper 组成,包含:
initialize
: 更新前操作close
: 更新后操作
例如:
js
ReactMount._renderNewRootComponent(...) // 会触发事务开始
- 所有的
setState
都在事务中集中处理 - 异步操作(如
setTimeout
)不在事务中,所以不能批量更新
🔴 8. 总结
- 永远不要 直接修改
this.state
setState
是异步的,不能立即拿到更新后的值- 多次
setState
会被批处理 - 在异步环境(如
setTimeout
)下setState
是同步的 - 利用回调或生命周期钩子获取更新后的值
- 小心在更新钩子中滥用
setState
,可能会引发无限循环