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

相关推荐
沙蒿同学4 分钟前
我把架构约定编译成了会变红的测试:Wails v2 + Go + Vue3 桌面脚手架实战
前端·后端·github
cpolar技术支持7 分钟前
本地 Playwright 测试报告怎么远程复盘?Trace Viewer 跑起来后,用 cpolar 分享失败现场
前端·自动化测试·测试工具·cpolar·playwright
wordbaby7 分钟前
企业级后台管理系统路由设计与最佳实践指南
前端
胡志辉的博客16 分钟前
【完全开源】IP 纯净度检测 可一键部署到自己的CF
前端·javascript·chrome·ip·chromium
Hilaku42 分钟前
作为面试官,我最怕遇到什么样的候选人?
前端·javascript·程序员
TiDi1 小时前
Pinia优化重复请求
前端
web3d5201 小时前
01-用 Leafletjs 10 分钟搭一张水利一张图(Vue3 + Vite 实战)
前端·javascript
晚安日记wanna1 小时前
Vue2 的 defineProperty 差在哪四层追问筛掉九成候选人
前端·vue.js·面试
TiDi1 小时前
吸顶导航交互实现
前端
kisshyshy2 小时前
从Props透传到自定义Hook:系统梳理React跨层级通信与逻辑复用
前端·架构·代码规范