React16源码: React中调度之batchedUpdates的源码实现

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.number
    this.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)的原理是什么呢?

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里面的事件体系产生的回调
    • 事件体系会主动调用 batchedUpdatesinteractiveUpdates 帮助我们设置上下文
    • 所以,在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 也不是立马就更新的
    • 进入异步更新,就会进入到异步调度的过程中

你可能感兴趣的:(React,React,Native,react.js,前端框架,前端)