batchedUpdates
1 )概述
- 在
requestWork
在中间, 会判断一个 isBatchingUpdates 做一些特定的操作 - batchedUpdates 是一个批量更新的操作, 什么是批量更新呢?先来看一个例子
2 )示例
index.js
import React from 'react'
import ReactDOM from 'react-dom'import App from './demos/batchedUpdates'ReactDOM.render(<App />, document.getElementById('root'))
demos/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()// 第二种场景,主动`batchedUpdates`// setTimeout(() => {// this.countNumber()// }, 0)// 第三种场景,setTimeout中没有`batchedUpdates`setTimeout(() => {batchedUpdates(() => this.countNumber())}, 0)}countNumber() {const num = this.state.numberthis.setState({number: num + 1,})console.log(this.state.number)this.setState({number: num + 2,})console.log(this.state.number)this.setState({number: num + 3,})console.log(this.state.number)}render() {return <button onClick={this.handleClick}>Num: {this.state.number}</button>}
}
- 在上述
countNumber
中定义了更改 state的方法 - 当点击 按钮时,里面的 num 会更改,回调函数是
handleClick
- 第一种情况
- 当直接调用
countNumber
时,页面显示 3,但是 console 面板分别输出 3次0 - 注意 只获取一次 num, 初始化是 0,执行了3次setState, 最后一次是 0 + 3 = 3
- 这是因为 batchUpdtes 的概念所导致
- 当直接调用
- 第二种情况
- 在一个延迟器中调用
countNumber
- 这时候页面显示 3,console面板中分别输出 1, 2, 3
- 这是我们符合预期的输出
- 在一个延迟器中调用
- 第三种情况
- 在延迟器中使用 batchedUpdates 这个 API,来调用
countNumber
- 这时候,页面显示 3,console面板 依次输出 3次0
- 在延迟器中使用 batchedUpdates 这个 API,来调用
- 第一种情况
- 那么它(batchedUpdates)的原理是什么呢?
3 ) 源码
进入到 requestWork 函数中的相关判断,可以在源码 nodemodules/react-dom/cjs/react-dom-development.js 中
找到 requestWork 进行debugger,重启 react项目,之后在 Chrome 中调试
if (isBatchingUpdates) {// Flush work at the end of the batch.if (isUnbatchingUpdates) {// ...unless we're inside unbatchedUpdates, in which case we should// flush it now.nextFlushedRoot = root;nextFlushedExpirationTime = Sync;performWorkOnRoot(root, Sync, true);}return;
}
-
第一种场景下
isBatchingUpdates
是 true,isUnbatchingUpdates
是 false- 这样,最终直接 return 了, 就没有进入后续的调度,就什么都没做
- 通过调用堆栈可知,3个 update 调用完成后,存入 updateQueue之后
- 会调用
performSyncWork
来帮助我们完成任务调度的过程,而非通过requestWork
来完成的任务调度 - 这样,任务更新队列就在一起把state更新了, 所以要等到 state 所有都变掉之后,才会执行state的更新
- 所以 console 面板中打印的,不是真正更新后的 state
- 这就是 batchUpdates 让我们在一个方法调用里面,所有的setState都是一次性更新到我们的state里面的
- 而不是每次都调用 setState 进入调度和渲染,以此来提升性能效率
-
第二种场景下
- 当 setTimeout 执行后,方法执行上下文已不是 handleClick这个函数了,而是 window 了
- 这时的 batchUpdates 已重置了
- 这时候调用
countNumber
就没有isBatchingUpdates
为 true 的情况了,就不会批量执行 - 所以,每次 setState 都会更新,导致我们每次打印的都是新的 state
- 这种会导致,我们的应用性能变得很低,所以这种是极不推荐的
-
第三种场景下
- 基于
batchedUpdates
的 API 调用countNumber
- 这个 API 的作用是 设置上下文,handleClick 是react里面的事件体系产生的回调
- 事件体系会主动调用
batchedUpdates
或interactiveUpdates
帮助我们设置上下文 - 所以,在react事件中大部分回调都是会帮助我们做批量更新的
- 而不会让我们在事件的回调中做多次的更新
- 这是react 事件绑定中的性能优化
- 基于
在 ReactFiberSchduler.js 中的 batchedUpdates
中
// TODO: Batching should be implemented at the renderer level, not inside
// the reconciler.
function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {const previousIsBatchingUpdates = isBatchingUpdates;isBatchingUpdates = true;try {return fn(a);} finally {isBatchingUpdates = previousIsBatchingUpdates;if (!isBatchingUpdates && !isRendering) {performSyncWork();}}
}
- 它设置
isBatchingUpdates = true;
,在设置之前记录前值const previousIsBatchingUpdates = isBatchingUpdates;
- 当我们的回调 fn 被调用之后,在 finally 中 恢复
isBatchingUpdates
成之前的值previousIsBatchingUpdates
- 之后,判断是否符合
!isBatchingUpdates && !isRendering
符合了,则调用performSyncWork
方法
同样,unbatchedUpdates
也是来自这个文件
// TODO: Batching should be implemented at the renderer level, not inside
// the reconciler.
function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {if (isBatchingUpdates && !isUnbatchingUpdates) {isUnbatchingUpdates = true;try {return fn(a);} finally {isUnbatchingUpdates = false;}}return fn(a);
}
- 它设置
isUnbatchingUpdates
为 true - 它也是上下文的关系,让我们在调用 fn 的时候有这么一个值是 true 的上下文
- 标记产生的不一样的变化
- 以上是批量更新的概念和原理
- 扩展
- 从上面我们知道
setState
这个方法是同步的, - 但是调用之后,并不标志 react 的状态立马更新
- 这个更新是需要根据当前环境的上下文来判断的
- 如果处于批量更新的情况下,state不是立马更新的
- 如果不处于批量更新的情况下,state 可能会立马更新
- 因为 有 ConcurrentMode 这种异步渲染的情况
- 这种情况下,state 也不是立马就更新的
- 进入异步更新,就会进入到异步调度的过程中
- 从上面我们知道