React16源码: React中的expirationTime过期时间的计算源码实现

expirationTime 的计算方式

  • 先看expirationTime相关的源代码,这里是异步的计算方式,它会有一个过期时间
  • 异步任务优先级比较低,可以被打断,防止一直被打断导致不能执行,所以React给它设置了 expirationTime 过期时间
  • 也就是在这个时间之前,都可以打断,但是如果某个时间点发现任务已经过期了,还没有被执行,则强制执行该任务
  • ReactDOM.render 当中,它计算 expirationTime 的地方
    • 在 ReactFiberReconciler.js 中的 updateContainer 函数中,通过 computeExpirationForFiber 方法来计算一个过期时间

      js 复制代码
      const current = container.current; // 参数2
      const currentTime = requestCurrentTime(); // 参数1 我们可以近似理解为: 当前时间到js加载完成的时间的时间差值即可
      const expirationTime = computeExpirationForFiber(currentTime, current);
    • requestCurrentTime 这个函数,来自于 ReactFiberScheduler.js 中

      js 复制代码
      function requestCurrentTime() {
        // 这里,我把官方注释移除
      
        // 这里表示 已经进入到 渲染阶段 了,在 ReactDOM.render 中这里不会匹配,会跳过
        // 在一次render中,如果我有一个新的任务进来了,要计算 expirationTime 发现现在处于渲染阶段,这时直接返回上次 render 开始的时间,再去计算 expirationTime
        // 好处是 前后两次计算出来的 expirationTime 是一样的,让这个任务提前进行调度
        if (isRendering) {
          // We're already rendering. Return the most recently read time.
          return currentSchedulerTime;
        }
        // Check if there's pending work.
        findHighestPriorityRoot();
        // 刚初始化的时候,这个条件是成立的
        if (
          nextFlushedExpirationTime === NoWork ||
          nextFlushedExpirationTime === Never
        ) {
          // If there's no pending work, or if the pending work is offscreen, we can
          // read the current time without risk of tearing.
          recomputeCurrentRendererTime();
          currentSchedulerTime = currentRendererTime; // 两个常量划等号
          return currentSchedulerTime;
        }
        // 这里,我把官方注释移除
        return currentSchedulerTime;
      }
      • findHighestPriorityRoot 方法涉及到从调度队列中找到权限最高的 Root

        • 这个源码比较多,不做扩展
      • recomputeCurrentRendererTime 每一次做计算都是从当前到js加载完成后的时间间隔,再经过一些计算得到的值,

        js 复制代码
        function recomputeCurrentRendererTime() {
          const currentTimeMs = now() - originalStartTimeMs; // 当前时间 - react buddle加载完成之后初始的时间,也就是从js加载完成到现在的时间间隔
          currentRendererTime = msToExpirationTime(currentTimeMs); // 计算出 currentRendererTime
        }
        
        // ReactFiberExpirationTime.js 这个函数得到一个时间戳
        export function msToExpirationTime(ms: number): ExpirationTime {
          // Always add an offset so that we don't clash with the magic number for NoWork.
          return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET; // UNIT_SIZE 是固定的 10, | 0 是取整的意思, MAGIC_NUMBER_OFFSET 是固定的 2
        }
    • 关于 expirationTime 的计算函数 computeExpirationForFiber 有一个计算公式

    • 在这个计算时间中,不需要考虑调度,只考虑计算公式,在 ReactFiberExpirationTime.js 中

      js 复制代码
      function ceiling(num: number, precision: number): number {
        return (((num / precision) | 0) + 1) * precision;
      }
      
      function computeExpirationBucket(
        currentTime,
        expirationInMs,
        bucketSizeMs,
      ): ExpirationTime {
        return (
          MAGIC_NUMBER_OFFSET +
          ceiling(
            currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE,
            bucketSizeMs / UNIT_SIZE,
          )
        );
      }
      
      // 低权限计算
      export function computeAsyncExpiration(
        currentTime: ExpirationTime,
      ): ExpirationTime {
        return computeExpirationBucket(
          currentTime,
          LOW_PRIORITY_EXPIRATION, // 5000
          LOW_PRIORITY_BATCH_SIZE, // 250
        );
      }
      
      // 高权限计算
      export function computeInteractiveExpiration(currentTime: ExpirationTime) {
        return computeExpirationBucket(
          currentTime,
          HIGH_PRIORITY_EXPIRATION, // 500/150 前 DEV, 后 PROD
          HIGH_PRIORITY_BATCH_SIZE, // 100
        );
      }
      • 上面两个 export 方法,都是调用 computeExpirationBucket 方法来计算的
      • 两个方法的区别在于 后面第2和第3个参数是不一样的
      • 最终得到的公式是: ((((currentTime - 2 + 5000 / 10) / 25) | 0 ) + 1) * 25
        • 其中 25 = 250 / 10,上述公式中的25,也可能是 10 (100 / 10)
          • 最终计算出来的 expirationTime 是以 bucketSize / UNIT_SIZE 这个单元向上叠加的
          • 两个 不同的 expirationTime 的差距是 单元值的 倍数
          • 对于 LOW_PRIORITY_BATCH_SIZE 是 以 25 为单元向上加的, 若前后差距在25以内,计算出来的差距都是一样的
          • 对于 HIGH_PRIORITY_BATCH_SIZE 是 以 10 为单元向上加的,同上
          • React 这么设定的原因: 在计算 expirationTime
          • 在一个操作内多次调用 setState, 即便前后调用差距很小,但从毫秒级别看,还是有差距的
          • 如果没有提供任何一个调整空间,即便上个 setState 和 下一个 setState 之间差距特别小,算出来的 expirationTime 结果不一样
          • 这就意味着,两次的任务优先级不一样, 会导致 React 整体更新执行多次,而导致整个应用的性能下降,这就是 设置 单元值的 用处
          • 在一个差距很小的时间间隔内,算出来的 expirationTime 结果一样,则它们优先级也是一样的,而不需要进行区分
          • 这个非常重要
        • | 0 表示 去余取整
        • 其中 (currentTime - 2 + 5000 / 10) 是一个不会变化的值,设为 x
        • 后面就是 (((x / 25) | 0) + 1) * 25
        • 这个公式的意义在于,新老值之间的差距在25以内,则结果相等
  • 至于 expirationTime 的作用还要结合后期更新的流程来看
  • expirationTime 是一个和业务无关的比较纯粹的计算过程问题,没有任何副作用
相关推荐
每一天,每一步2 小时前
react antd点击table单元格文字下载指定的excel路径
前端·react.js·excel
screct_demo12 小时前
詳細講一下在RN(ReactNative)中,6個比較常用的組件以及詳細的用法
javascript·react native·react.js
光头程序员20 小时前
grid 布局react组件可以循数据自定义渲染某个数据 ,或插入某些数据在某个索引下
javascript·react.js·ecmascript
limit for me20 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者20 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架
VillanelleS1 天前
React进阶之高阶组件HOC、react hooks、自定义hooks
前端·react.js·前端框架
傻小胖1 天前
React 中hooks之useInsertionEffect用法总结
前端·javascript·react.js
flying robot1 天前
React的响应式
前端·javascript·react.js
GISer_Jing2 天前
React+AntDesign实现类似Chatgpt交互界面
前端·javascript·react.js·前端框架
智界工具库2 天前
【探索前端技术之 React Three.js—— 简单的人脸动捕与 3D 模型表情同步应用】
前端·javascript·react.js