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

这篇具有很好参考价值的文章主要介绍了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 也是来自这个文件文章来源地址https://www.toymoban.com/news/detail-799380.html

// 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 也不是立马就更新的
    • 进入异步更新,就会进入到异步调度的过程中

到了这里,关于React16源码: React中调度之batchedUpdates的源码实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • React16源码: React中的IndeterminateComponent的源码实现

    IndeterminateComponent 1 )概述 这是一个比较特殊的component的类型, 就是还没有被指定类型的component 在一个fibrer被创建的时候,它的tag可能会是 IndeterminateComponent 在 packages/react-reconciler/src/ReactFiber.js 中,有一个方法 createFiberFromTypeAndProps 中,一开始就声明了 在最终调用 createFibe

    2024年01月21日
    浏览(41)
  • React16源码: React中的completeUnitOfWork的源码实现

    completeUnitOfWork 1 )概述 各种不同类型组件的一个更新过程对应的是在执行 performUnitOfWork 里面的 beginWork 阶段 它是去向下遍历一棵 fiber 树的一侧的子节点,然后遍历到叶子节点为止,以及 return 自己 child 的这种方式 在 performUnitOfWork 里面,还有一个方法叫做 completeUnitOfWork 在

    2024年01月23日
    浏览(42)
  • React16源码: React中的unwindWork的源码实现

    unwindWork 1 )概述 在 renderRoot 的 throw Exception 里面, 对于被捕获到错误的组件进行了一些处理 并且向上去寻找能够处理这些异常的组件,比如说 class component 里面具有 getDerivedStateFromError 或者 componentDidCatch 这样的生命周期方法 这个class component 就代表它可以处理它的子树当中渲

    2024年01月25日
    浏览(36)
  • React16源码: React中的performWork的源码实现

    performWork 1 )概述 performWork 涉及到在调度完成,或者同步任务进来之后整个 root 节点链条如何更新 怎么更新一棵 Fiber 树,它的每一个节点是如何被遍历到,以及如何进行更新操作 A. 在执行 performWork 时候,是否有 deadline 的区分 deadline 是通过 reactschedule 它的一个时间片,更新

    2024年01月17日
    浏览(39)
  • React16源码: React中的updateHostRoot的源码实现

    HostRoot 的更新 1 )概述 HostRoot 是一个比较特殊的节点, 因为在一个react应用当中 它只会有一个 HostRoot , 它对应的 Fiber 对象是我们的 RootFiber 对象 重点在于它的更新过程 2 )源码 定位到 packages/react-reconciler/src/ReactFiberBeginWork.js#L612 HostRoot 创建更新的过程就是在 ReactFiberReconcile

    2024年01月22日
    浏览(43)
  • React16源码: React中的updateClassComponent的源码实现

    ClassComponent 的更新 1 ) 概述 在 react 中 class component,是一个非常重要的角色 它承担了 react 中 更新整个应用的API setState forceUpdate 在react当中,只有更新了state之后,整个应用才会重新进行渲染 在 class component 中, 它的逻辑相对复杂 2 )源码 在 packages/react-reconciler/src/ReactFiberB

    2024年01月21日
    浏览(38)
  • React16源码: React中的beginWork的源码实现

    beginWork 1 )概述 在 renderRoot 之后,要对我们的 Fiber 树每一个节点进行对应的更新 更新节点的一个入口方法,就是 beginWork 这个入口方法会有帮助我们去优化整棵树的更新过程 react 它的节点其实是非常多的,如果每一次子节点的一个更新 就需要每一个节点都执行一遍更新的话

    2024年01月20日
    浏览(46)
  • React16源码: React中的renderRoot的源码实现

    renderRoot 1 )概述 renderRoot 是一个非常复杂的方法 这个方法里处理很多各种各样的逻辑, 它主要的工作内容是什么? A. 它调用 workLoop 进行循环单元更新 遍历整个 Fiber Tree,把每一个组件或者 dom 节点对应的 Fiber 节点拿出来单一的进行更新,这是一个循环的操作 把整棵 Fiber T

    2024年01月17日
    浏览(42)
  • React16源码: React中的reconcileChildren的源码实现

    reconcileChildren 1 )概述 在更新了一个节点之后,拿到它的props.children 要根据这个children里面的 ReactElement 来去创建子树的所有的 fiber 对象 要根据 props.children 来生成 fiber 子树,然后判断 fiber 对象它是否是可以复用的 因为我们在第一次渲染的时候,就已经渲染了整个 fiber 子树

    2024年01月20日
    浏览(40)
  • React16源码: React中的setState和forceUpdate源码实现

    setState 和 forceUpdate 1 ) 概述 通过 class component 内部的 setState ,以及 forceUpdate 去更新一个组件的过程 在react的应用当中,我们只有 ReactDOM.render setState ,以及 forceUpdate 这几种种方式去更新react的应用是合理的,其他没有什么特别常用的方式去更新了 而且react官方推荐的也是用

    2024年01月25日
    浏览(38)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包