节选自 React 源码笔记(尚未研究完),由于知识粒度较为合适,所以单独先贴出来。
顾名思义,批量更新,可以避免短期内的多次渲染,攒为一次性更新。
在后面提供的 demo 中的 handleClick
中有三种方式调用 this.countNumber()
:
- 事件处理函数自带batchedUpdates
- setTimeout中没有batchedUpdates
- 主动batchedUpdates
第1种:
批量更新,会打印 0 0 0,然后按钮文本显示为3。每次 setState
虽然都会经过 enqueueUpdate
(创建update 并加入队列)-> scheduleWork
(寻找对应的 FiberRoot 节点)-> requestWork
(把 FiberRoot 加入到调度队列),可惜上下文变量 isBatchingUpdates
在外部某个地方被标记为了 true
,因此本次 setState
一路走来,尚未到达接下来的 performSyncWork
或者 scheduleCakkbackWithExpirationTime
就开始一路 return 出栈:

isBatchingUpdates
变量在早前的调用栈中(我们为 onClick 绑定的事件处理函数会被 react 包裹多层),被标记为了 true
,然后 fn(a, b)
内部经过了3次 setState
系列操作,然后 finally 中 isBatchingUpdates
恢复为之前的 false,此时执行同步更新工作 performSyncWork
:

第2种:
在 handleClick
中使用 setTimeout
将 this.countNumber
包裹了一层 setTimeout(() => { this.countNumber()}, 0)
,同样要调用 handleClick
也是先经过 interactiveUpdates$1
上下文,也会执行 setTimeout
,然后 fn(a, b)
就执行完了,因为最终是浏览器来调用 setTimeout
的回调 然后执行里面的 this.countNumber
,而对于 interactiveUpdates$1
来说继续把自己的 performSyncWork
执行完,就算结束了。显然不管 performSyncWork
做了什么同步更新,我们的 setState
目前为止都还没得到执行。然后等到 setTimeout
的回调函数等到空闲被执行的时候,才会执行 setState
,此时没有了批量更新之上下文,所以每个 setState
都会单独执行一遍 requestWork
中的 performSyncWork
直到渲染结束,且不会被打断,3次 setState
就会整个更新渲染 3 遍(这样性能不好,所以一般不会这样写 react)。
什么叫不会被打断的同步更新渲染?看一下 demo 中的输出,每次都同步打印出了最新的 button dom 的 innerText
。
第3种:
已经可以猜到,无非就是因为使用 setTimeout
而“错过了”第一次的批量更新上下文,那等到 setTimeout
的回调执行的时候,专门再创建一个批量更新上下文即可:

总结:
setState 是同步还是异步?
setState 方法本身是被同步调用,但并不代表 react 的 state 就会被立马同步地更新,而是要根据当前执行上下文来判断。
如果处于批量更新的情况下,state 不会立马被更新,而是批量更新。
如果非批量更新的情况下,那么就“有可能”是立马同步更新的。为什么不是“一定”?因为如果 React 开启了 Concurrent Mode,非批量更新会进入之前介绍过的异步调度中(时间分片)。
批量更新演示 demo:
import React from 'react'
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom'
export default class BatchedDemo extends React.Component {
state = {
number: 0,
}
handleClick = () => {
// 事件处理函数自带batchedUpdates
this.countNumber()
// setTimeout中没有batchedUpdates
// setTimeout(() => {
// this.countNumber()
// }, 0)
// 主动batchedUpdates
// setTimeout(() => {
// batchedUpdates(() => this.countNumber())
// }, 0)
}
countNumber() {
const button = document.getElementById('myButton')
const num = this.state.number
this.setState({
number: num + 1,
})
console.log(this.state.number)
console.log(button.innerText)
this.setState({
number: num + 2,
})
console.log(this.state.number)
console.log(button.innerText)
this.setState({
number: num + 3,
})
console.log(this.state.number)
console.log(button.innerText)
}
render() {
return <button id="myButton" onClick={this.handleClick}>Num: {this.state.number}</button>
}
}
网友评论