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

相关推荐
小哥不太逍遥14 分钟前
Technical Report 2024
java·服务器·前端
沐墨染18 分钟前
黑词分析与可疑对话挖掘组件的设计与实现
前端·elementui·数据挖掘·数据分析·vue·visual studio code
anOnion23 分钟前
构建无障碍组件之Disclosure Pattern
前端·html·交互设计
threerocks26 分钟前
前端将死,Agent 永生
前端·人工智能·ai编程
问道飞鱼1 小时前
【前端知识】Vite用法从入门到实战
前端·vite·项目构建
爱上妖精的尾巴1 小时前
8-10 WPS JSA 正则表达式:贪婪匹配
服务器·前端·javascript·正则表达式·wps·jsa
shadow fish2 小时前
react学习记录(三)
javascript·学习·react.js
Aliex_git3 小时前
浏览器 API 兼容性解决方案
前端·笔记·学习
独泪了无痕3 小时前
useStorage:本地数据持久化利器
前端·vue.js
程序员林北北3 小时前
【前端进阶之旅】JavaScript 一些常用的简写技巧
开发语言·前端·javascript