react-this.setState是同步还是异步的
- 如果需要依赖更新后的值,如果是
classComponent,我们可以在componentDidUpdate中执行,或者this.setState的第二个参数。如果是functionComponent,我们可以在useEffect中执行 - 不同模式下的 react,这个答案是不一样的
react 的三种模式:
legacy,react 当前使用的模式,ReactDOM.renderblocking,开启部分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 模式都是异步