从 React BatchUpdates 逻辑分析,看 React 是 如何优化性能的?
前言
React
16.8 版本之前 关于多次调用 setState
时候逻辑,里面有一个优化性能,批量更新的优化,我们就来看看,到底底层的逻辑是什么,本次的例子是运行在 React
15 版本上,看看 React
到底是如何优化性能的
batchUpdate 概念
事件处理函数自带 batchedUpdates
调用多次 setState
将会开启,batchUpdate
会将多次 setState
合并到一次 React
任务更新中,然后依次调用 setState
下面我们观察 this.state.number
的变化,从而深入了解 batchedUpdates
的过程
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()
}
countNumber() {
const num = this.state.number
this.setState({
number: num + 1,
})
console.log(this.state.number) // 0 这里 state 并没有被更新
this.setState({
number: num + 2,
})
console.log(this.state.number) // 0 这里 state 并没有被更新
this.setState({
number: num + 3,
})
console.log(this.state.number) // 0 这里 state 并没有被更新
}
// 这里调用 点击事件
// 点击后当前的 state 是 3 显示在页面上
render() {
return <button onClick={this.handleClick}>Num: {this.state.number}</button>
}
}
debug
我们在 React
源码中找到 requestWork
在里面进行 添加一个断点 debugger
, 我们可以观察到
因为 isBatchingUpdates
赋值为 true
那么 将直接 return
不进行后面的 performSynvWork
操作, 所以我们可以理解 state
并没有发生更新,同样我们在 enqueueSetState
中是可以看到已经创建好更新,已经在更新队列中
源码部分
我们继续深入进行观察,看看到底 isBatchingUpdates
状态是如何发生变化的
我们看到源码上,其实我们重点关注到的是 previousIsBatchingUpdates
,这个变量会保存之前的 isBatchingUpdates
状态,在最后 finally
重新赋值到 isBatchingUpdates
,然后在一起批量更新。
所以出现这种情况的原因我就顺利找到了
// TODO: Batching should be implemented at the renderer level, not inside
// the reconciler.
function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
// 当前是否 批量更新赋值到 previous 状态上
const previousIsBatchingUpdates = isBatchingUpdates;
isBatchingUpdates = true;
try {
return fn(a); // 这里调用的是 实际上是 handleClick 方法
} finally {
// 将过去上一次更新的 previous 存到全局变量 BatchingUpdates 上
isBatchingUpdates = previousIsBatchingUpdates;
// 当不是批量更新 而且不是在渲染阶段,那么state的值将会一次更新,调用 performSyncWork
if (!isBatchingUpdates && !isRendering) {
performSyncWork(); // 直接同步一起更新 所以这里我们可以
}
}
}
setTimeout 中特殊情况
对 setTimeout
的思考:因为 setTimeout
是一个宏任务,从内存角度来说,和之前任务不是在同一个栈中。所以执行到这个宏任务的时候,之前的栈中数据会被还原(isBatchingUpdates
是初始值false
),所以不是批量更新。也可以这样去理解 setTimeout
在宏任务中,调用的事件是没有开启批量更新的
import React from 'react'
export default class BatchedDemo extends React.Component {
state = {
number: 0,
}
handleClick = () => {
// 这样就不会开启 batchUpdate isBatchingUpdates 属性就会为 false
setTimeout(() => {
this.countNumber()
}, 0)
}
countNumber() {
const num = this.state.number
this.setState({
number: num + 1,
})
console.log(this.state.number) // 1
this.setState({
number: num + 2,
})
console.log(this.state.number) // 2
this.setState({
number: num + 3,
})
console.log(this.state.number) // 3
}
...
}
在 setTimeout 中 强行调用 实现 batchUpdate
我们尝试一下在 setTimeout 宏任务中强行调用 batchedUpdates
,这次同 例子一 逻辑是一致的
import React from 'react'
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom'
export default class BatchedDemo extends React.Component {
state = {
number: 0,
}
handleClick = () => {
// setTimeout中没有`batchedUpdates`
setTimeout(() => {
batchedUpdates(() => this.countNumber())
}, 0)
}
...
}
总结
在上面的例子中,我们大概了解到了 batchUpdate
的过程,下面我们总结一下具体流程。其实关键的一步是 previousIsBatchingUpdates
记录上一次的 isBatchingUpdates
状态赋值给下一次, 依次执行 setState
后,就会将之前的状态赋值到下一次更新的队列中,最后开始执行更新应用 performSyncWork
,这里的原因是 React
为了提供性能而做出的优化
转载自:https://juejin.cn/post/7062928241920573453