react-this.setState是同步还是异步的
- 如果需要依赖更新后的值,如果是
classComponent
,我们可以在componentDidUpdate
中执行,或者this.setState
的第二个参数。如果是functionComponent
,我们可以在useEffect
中执行 - 不同模式下的 react,这个答案是不一样的
react 的三种模式:
legacy
,react 当前使用的模式,ReactDOM.render
blocking
,开启部分concurrent
模式特性的中间模式concurrent
,react v18 启用。任务中断/任务优先级
都是针对concurrent
模式
lagecy 模式
lagecy 模式下,this.setState
触发的更新是异步的。因为它会命中batchedUpdates
函数。
batchedUpdates
定义:
batchedUpdates 表示批处理,react 会将多次**this.setState**
合并为一次更新,并异步执行。这样只会触发一次render函数
,以此提高性能。
实现:fn
:包含this.setState
的函数,比如点击事件、生命周期等。
react 内部有个batchedUpdates
函数,在执行这个fn
之前,会给全局变量executionContext
附加上BatchedContex
这个 flag
1 | export function batchedUpdates(fn, a) { |
然后它会执行这个 fn,执行完之后,会把BatchedContex
从executionContext
去除。
如果全局变量executionContext
包含了BatchedContext
,他就会认为这是一次批处理。批处理中的setState
会被合并为一次更新。
以 classComponent 为例
例子一:
1 | import { PureComponent } from 'react'; |
在onAdd
中调用this.setState
,会被认为是批处理,并不能马上获取到最新的count
。可以在componentDidUpdate
钩子中或者this.setState
的第二个参数中获取到最新值。
如何跳出batchedUpdates
如果fn
中触发的this.setState
是异步执行的话,等this.setState
执行的时候,全局的executionContext
就已经不存在BatchedContext
,他就会跳出批处理。
跳出批处理后,每次调度更新都会执行scheduleUpdateOnFiber
函数,函数内部有段逻辑:
1 | // react V < 18 |
也就是说,如果executionContext
什么都没有的话,会执行fulshSyncCallbackQueue
函数,同步的执行这次更新。
例子二:
1 | onAdd () { |
concurrent 模式
将ReactDOM.render(<App />, rootNode)
变为为ReactDOM.createRoot(rootNode).render(<App />)
,这样就切换到了concurrent
模式下。concurrent
模式下,例子一的表现结果与laecy
模式一致。
例子二,after 的输出 从2
变为0
,componentDidMount
里的输出两次1和2
变为输出一次1
,这是为什么呢?
这是因为,执行scheduleUpdateOnFiber
函数的前提是本次更新的优先级是同步的优先级lane === SyncLane
。那什么是同步的优先级呢,也就是ReactDOM.render
创建的应用的更新都是同步的优先级,而concurrent
模式创建的应用有不同的优先级,所以不会命中fulshSyncCallbackQueue
,即同步的更新。
总结
- lagecy 模式命中
batchedUpdates
时异步 - lagecy 模式未命中
batchedUpdates
时同步 - concurrent 模式都是异步