React16源码: React中的不同的expirationTime的源码实现

不同的 expirationTime

1 )概述

  • 在React中不仅仅有异步任务
  • 大部分情况下都是同步的任务,所以会有不同 expirationTime 的存在

2 )种类

  • A. Sync 模式,优先级最高
    • 任务创建完成之后,立马更新到真正的dom里面
    • 是一个创建即更新的流程
  • B. Async 模式, 异步模式
    • 会有一个调度
    • 包含一系列复杂的操作在里面
    • 可能会中断,所以会有一个计算出来的过期时间
    • 过期时间根据异步的两种情况,如前文所述
      • 最低优先级的异步
      • 最高优先级的异步
  • C. 指定context

3 ) 源码

  • 在 ReactFiberReconciler.js 中的 updateContainer
    • 调用的是 computeExpirationForFiber

      • 它接收两个参数: currentTime: ExpirationTime, fiber: Fiber
    • 这个方法来自于 ReactFiberScheduler.js 文件

      js 复制代码
      function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
        let expirationTime;
        if (expirationContext !== NoWork) {
          // An explicit expiration context was set;
          expirationTime = expirationContext;
        } else if (isWorking) {
          if (isCommitting) {
            // Updates that occur during the commit phase should have sync priority
            // by default.
            expirationTime = Sync;
          } else {
            // Updates during the render phase should expire at the same time as
            // the work that is being rendered.
            expirationTime = nextRenderExpirationTime;
          }
        } else {
          // No explicit expiration context was set, and we're not currently
          // performing work. Calculate a new expiration time.
          if (fiber.mode & ConcurrentMode) {
            if (isBatchingInteractiveUpdates) {
              // This is an interactive update
              expirationTime = computeInteractiveExpiration(currentTime);
            } else {
              // This is an async update
              expirationTime = computeAsyncExpiration(currentTime);
            }
            // If we're in the middle of rendering a tree, do not update at the same
            // expiration time that is already rendering.
            if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
              expirationTime += 1;
            }
          } else {
            // This is a sync update
            expirationTime = Sync;
          }
        }
        if (isBatchingInteractiveUpdates) {
          // This is an interactive update. Keep track of the lowest pending
          // interactive expiration time. This allows us to synchronously flush
          // all interactive updates when needed.
          if (expirationTime > lowestPriorityPendingInteractiveExpirationTime) {
            lowestPriorityPendingInteractiveExpirationTime = expirationTime;
          }
        }
        return expirationTime;
      }
      • 1 )这里有一系列的判断,首先是对 expirationContext 的判断
        • 它是 ExpirationTime 类型,默认初始化值是 NoWork

        • 下面是它的一个修改场景

          js 复制代码
          function deferredUpdates<A>(fn: () => A): A {
            const currentTime = requestCurrentTime();
            const previousExpirationContext = expirationContext;
            const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;
            expirationContext = computeAsyncExpiration(currentTime); // 这里
            isBatchingInteractiveUpdates = false;
            try {
              return fn();
            } finally {
              expirationContext = previousExpirationContext;
              isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;
            }
          }
          • expirationContext = computeAsyncExpiration(currentTime);
          • 可以看到 computeAsyncExpiration 这个函数对其进行修改赋值
        • 还有一种场景, 往下进行搜索

          js 复制代码
          function syncUpdates<A, B, C0, D, R>(
            fn: (A, B, C0, D) => R,
            a: A,
            b: B,
            c: C0,
            d: D,
          ): R {
            const previousExpirationContext = expirationContext;
            expirationContext = Sync; // 变成了 Sync
            try {
              return fn(a, b, c, d); // 这里是 setState 操作
            } finally {
              expirationContext = previousExpirationContext; // 最终还原
            }
          }
          • syncUpdates 在 ReactDOM.js 中的 flushSync API
            • flushSync: DOMRenderer.flushSync 一直溯源往上找到 ReactFiberScheduler.js 中的 flushSync

            • 这个就是 本源的 flushSync

              js 复制代码
              // TODO: Batching should be implemented at the renderer level, not within
              // the reconciler.
              function flushSync<A, R>(fn: (a: A) => R, a: A): R {
                invariant(
                  !isRendering,
                  'flushSync was called from inside a lifecycle method. It cannot be ' +
                    'called when React is already rendering.',
                );
                const previousIsBatchingUpdates = isBatchingUpdates;
                isBatchingUpdates = true;
                try {
                  return syncUpdates(fn, a); // 这里
                } finally {
                  isBatchingUpdates = previousIsBatchingUpdates;
                  performSyncWork();
                }
              }
            • 上述调用的是 syncUpdates,在之前的示例中,如下

              js 复制代码
              flushSync(() => {
                this.setState({
                  num: newNum,
                })
              })
            • 上述示例就是传入 fn 回调, 内部调用 setState

            • 在这种场景下 expirationTime 变成了 Sync

        • 以上两种情况 是给 expirationContext 进行赋值

      • 2 )当 expirationTime 变成了 Sync 就不符合第一种情况了,这时候往下走,匹配到了 isWorking
        • isWorking 表示有任务正在更新
        • 也是基于条件指定某个值
        • 这块涉及到后续任务的更新,跳过
      • 3 )没有外部强制的情况下
        • if (fiber.mode & ConcurrentMode)

        • 判断 fiber.mode 是否是 ConcurrentMode

        • 找到 ConcurrentMode 的定义处 ReactTypeOfMode.js

          js 复制代码
          export type TypeOfMode = number;
          
          export const NoContext = 0b000;
          export const ConcurrentMode = 0b001;
          export const StrictMode = 0b010;
          export const ProfileMode = 0b100;
        • 可见,使用二进制方式来定义的

        • 可以通过 与或 这类逻辑的操作,非常方便的组合 Mode

        • 以及判断是否有某个Mode,例如

          js 复制代码
          const a = 0b000;
          const b = 0b001;
          const c = 0b010;
          const d = 0b100;
          let mode = a; // 默认等于 a, 在后续渲染时并不知道是否有更改过
          mode & b // 如果 结果为 0 表示没有过b这种情况
          mode |= b // 这样给 mode 增加 b,这时候 mode 变成1,就对应了 b
          mode |= c // 给 mode 增加 c, 这时候 mode 变成 3,也就是 0b011
          mode & b // 这时候判断mode是否有b, 如果是1,则有b, 结果是1 对应b
          mode & c // 这时候判断mode是否有c, 如果是1,则有c, 结果是2 对应c
          • 这是一种巧妙的设计方式
          • 类似的,在 ReactSideEffectTags.js 中也是这么设计的
        • 回到代码里,如果是 ConcurrentMode 才会调用

        • computeInteractiveExpirationcomputeAsyncExpiration 两个函数中的一个计算 expirationTime

          js 复制代码
          if (isBatchingInteractiveUpdates) {
            // This is an interactive update
            expirationTime = computeInteractiveExpiration(currentTime);
          } else {
            // This is an async update
            expirationTime = computeAsyncExpiration(currentTime);
          }
          • 关于 isBatchingInteractiveUpdatesinteractiveUpdates 函数中被赋值为 true
            • 大部分的 React 事件产生的更新,事件中绑定回调函数,这个回调函数执行的时候
            • 大部分都是在 interactiveUpdates 情况下,也就是 isBatchingInteractiveUpdates 为 true 时
          • 后面 expirationTime += 1; 是为了区分下一个即将进行的更新和当前正在创造的更新,防止一样,强制把当前+1
        • 如果不属于,则还是 Sync

相关推荐
世俗ˊ20 分钟前
CSS入门笔记
前端·css·笔记
子非鱼92120 分钟前
【前端】ES6:Set与Map
前端·javascript·es6
6230_25 分钟前
git使用“保姆级”教程1——简介及配置项设置
前端·git·学习·html·web3·学习方法·改行学it
想退休的搬砖人34 分钟前
vue选项式写法项目案例(购物车)
前端·javascript·vue.js
加勒比海涛1 小时前
HTML 揭秘:HTML 编码快速入门
前端·html
啥子花道1 小时前
Vue3.4 中 v-model 双向数据绑定新玩法详解
前端·javascript·vue.js
麒麟而非淇淋1 小时前
AJAX 入门 day3
前端·javascript·ajax
茶茶只知道学习1 小时前
通过鼠标移动来调整两个盒子的宽度(响应式)
前端·javascript·css
清汤饺子1 小时前
实践指南之网页转PDF
前端·javascript·react.js
蒟蒻的贤1 小时前
Web APIs 第二天
开发语言·前端·javascript