- Published on
- 约 623 字
实现一个简单的 React.js 18 自动批量更新功能
- Authors
- Name
- 小辉辉
前言
React.js 18之前批量更新state是否是异步操作取决于调用时机,具体表现为以下几种场景:
如果是在事件内部调用的链路则是异步的,例如click点击事件,change值变化事件
在事件内部链路用异步包裹处理,例如setTimeout、Promise则是同步的
其余的地方一律都是同步的
自从更新到React.js 18版本之后,默认上述setState行为全部变成了异步操作,这对整体组件性能优化提升了不少,因为在同一个操作里面所有更新都变成了批量的操作,组件重新渲染的次数就更少了只合并为了一次。
基本实现原理
我们通过打断点的形式,可以很快速的得出React18现在的批量更新实现形式,主要是借助于MessageChannel这个功能来实现,对应步骤如下:
- React内部维护了一个MessageChannel实例mc
- mc对应的port1监听setState调用时触发的更新操作
- 内部维护一个待更新操作update,用于缓存最近的state值
- 每次调用setState时,会更新update,还有向mc的port2发送消息
- mc的port2在接收到第一次更新后,会标记内部更新状态isUpdating,避免多次发送操作
- 所有setState操作完后,mc的port1开始响应port2发送的消息,执行update更新操作
至此一个简单的批量更新操作实现完成。
示例代码
class ReactAutomaticBatchUpdate {
mc = new MessageChannel()
state = { num: 0 }
update
isUpdating = false
constructor() {
this.start()
this.initMc()
this.initPromise()
this.initSetTimetout()
this.logNum('before setState')
this.setState({ num: 1 })
this.logNum('first after setState')
this.setState({ num: this.state.num + 1 })
this.end()
}
start() {
this.logNum('start')
}
end() {
this.logNum('end')
}
initPromise() {
Promise.resolve().then(() => {
this.logNum('promise')
})
}
initSetTimetout() {
setTimeout(() => {
this.logNum('timeout 0')
})
setTimeout(() => {
this.logNum('timeout 1')
}, 1)
}
handlePortMsg = () => {
this.state = this.update
this.isUpdating = false
console.log('after msg')
}
initMc() {
this.mc.port1.onmessage = this.handlePortMsg
}
sendData() {
if (!this.isUpdating) {
this.isUpdating = true
this.mc.port2.postMessage(null)
}
}
setState(data) {
this.update = data
this.sendData()
}
logNum(type) {
console.log(type, this.state.num)
}
}
new ReactAutomaticBatchUpdate()
输出结果
start 0
before setState 0
first after setState 0
end 0
promise 0
timeout 0 0
after msg
timeout 1 1
通过结果我们能够发现以下几点:
- 每次setState操作后state没有及时更新,批量更新实现
- promise先于setTimeout、MessageChannel调用
- timeout 0先于MessageChannel调用
- timeout 1在MessageChannel后调用
最后关于React 18自动批量更新更多的说明还可以参考官方这篇文章。