先来思考一个老生常谈的问题,setState是同步还是异步?
再深入思考一下,useState是同步还是异步呢?
我们来写几个 demo 试验一下。
先看 useState
同步和异步情况下,连续执行两个 useState 示例
1 | function Component() { |
结论:
- 当点击
同步执行
按钮时,只重新 render 了一次 - 当点击
异步执行
按钮时,render 了两次
同步和异步情况下,连续执行两次同一个 useState 示例
1 | function Component() { |
- 当点击
同步执行
按钮时,两次 setA 都执行,但合并 render 了一次,打印 3 - 当点击
异步执行
按钮时,两次 setA 各自 render 一次,分别打印 2,3
再看 setState
同步和异步情况下,连续执行两个 setState 示例
1 | class Component extends React.Component { |
- 当点击
同步执行
按钮时,只重新 render 了一次 - 当点击
异步执行
按钮时,render 了两次
跟useState的结果一样
同步和异步情况下,连续执行两次同一个 setState 示例
1 | class Component extends React.Component { |
- 当点击
同步执行
按钮时,两次 setState 合并,只执行了最后一次,打印 2 - 当点击
异步执行
按钮时,两次 setState 各自 render 一次,分别打印 2,3
这里跟useState不同,同步执行时useState也会对state进行逐个处理,而setState则只会处理最后一次
为什么会有同步执行和异步执行结果不同呢?
这里就涉及到 react 的 batchUpdate 机制,合并更新。
- 首先,为什么需要合并更新呢?
如果没有合并更新,在每次执行 useState 的时候,组件都要重新 render 一次,会造成无效渲染,浪费时间(因为最后一次渲染会覆盖掉前面所有的渲染效果)。
所以 react 会把一些可以一起更新的 useState/setState 放在一起,进行合并更新。
- 怎么进行合并更新
这里 react 用到了事务机制。
React 中的 Batch Update 是通过「Transaction」实现的。在 React 源码关于 Transaction 的部分,用一大段文字及一幅字符画解释了 Transaction 的作用:
1 | * wrappers (injected at creation time) |
用大白话说就是在实际的 useState/setState 前后各加了段逻辑给包了起来。只要是在同一个事务中的 setState 会进行合并(注意,useState不会进行state的合并)处理。
- 为什么 setTimeout 不能进行事务操作
由于 react 的事件委托机制,调用 onClick 执行的事件,是处于 react 的控制范围的。
而 setTimeout 已经超出了 react 的控制范围,react 无法对 setTimeout 的代码前后加上事务逻辑(除非 react 重写 setTimeout)。
所以当遇到 setTimeout/setInterval/Promise.then(fn)/fetch 回调/xhr 网络回调
时,react 都是无法控制的。
相关react 源码如下
1 | if (executionContext === NoContext) { |
executionContext 代表了目前 react 所处的阶段,而 NoContext 你可以理解为是 react 已经没活干了的状态。而 flushSyncCallbackQueue 里面就会去同步调用我们的 this.setState ,也就是说会同步更新我们的 state 。所以,我们知道了,当 executionContext 为 NoContext 的时候,我们的 setState 就是同步的
总结
我们来总结一下上述实验的结果:
- 在同步事件中
- 多次执行setState和useState,只会调用一次重新渲染render
- 不同的是,setState会进行state的合并,而useState则不会
- 在异步的事件中(如setTimeout,Promise.then)
- 多次执行setState和useState,每一次的执行setState和useState,都会调用一次render
参考资料
- https://github.com/facebook/react/blob/b1768b5a48d1f82e4ef4150e0036c5f846d3758a/src/renderers/shared/stack/reconciler/Transaction.js#L19-L54
- https://zhuanlan.zhihu.com/p/78516581
- https://github.com/facebook/react/issues/10231
- https://zhuanlan.zhihu.com/p/28532725
- https://medium.com/swlh/react-state-batch-update-b1b61bd28cd2
- https://zhuanlan.zhihu.com/p/350332132
- https://juejin.cn/post/6844903886407352334