React Expiration Time 浅谈

  Expiration Time 概念

  首先 Expiration Time 到底是什么呢? 根据英文直接翻译可知,到期时间或者过期时间。在React中到期时间概念又如何理解,我们不妨从它的作用入手理解到底是什么概念。

  Expiration Time 作用

  在 React 中,源码位置是在 准备阶段 updateContainer 的位置 调用 computeExpirationForFiber 计算时间,这里是在准备阶段创建好React的更新对象,为后面的后面 React 调度做准备。它代表的是 任务在未来的哪个时间点上应该被执行,不然它就过期了。具体可以查看 react-reconciler 包中 ReactFiberExpirationTime.js 具体的代码内容

  总结一下:React 在创建更新的过程 为了后面更新调度的时候,合理安排更新顺序,React 会设置一个过期时间(Expiration Time),当 Expiration-Time 到了以后,就会强制更新。

  具体源码内容

  源码因为版本不一样,会有大同小异,这里不做具体分析

  export function updateContainer(

  element: ReactNodeList,

  container: OpaqueRoot,

  parentComponent: ?React$Component<any, any>,

  callback: ?Function,

  ): ExpirationTime {

  // 获取当前 更新的 Fiber 节点

  const current = container.current;

  // 获取当前的时间

  const currentTime = requestCurrentTime();

  // 计算 ExpirationTime

  const expirationTime = computeExpirationForFiber(currentTime, current);

  return updateContainerAtExpirationTime(

  element,

  container,

  parentComponent,

  expirationTime,

  callback,

  );

  }

  复制代码

  如何计算 Expiration Time

  首先我们看 Expiration Time 代码,这里只是涉及到计算方式

  /**

  * Copyright (c) Facebook, Inc. and its affiliates.

  *

  * This source code is licensed under the MIT license found in the

  * LICENSE file in the root directory of this source tree.

  *

  * @flow

  */

  import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';

  export type ExpirationTime = number;

  export const NoWork = 0;

  export const Sync = 1;

  export const Never = MAX_SIGNED_31_BIT_INT;

  const UNIT_SIZE = 10;

  const MAGIC_NUMBER_OFFSET = 2;

  // 1 unit of expiration time represents 10ms.

  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;

  }

  export function expirationTimeToMs(expirationTime: ExpirationTime): number {

  return (expirationTime - MAGIC_NUMBER_OFFSET) * UNIT_SIZE;

  }

  function ceiling(num: number, precision: number): number {

  return (((num / precision) | 0) + 1) * precision;

  }

  // 核心内容

  function computeExpirationBucket(

  currentTime,

  expirationInMs,

  bucketSizeMs,

  ): ExpirationTime {

  // currentTime 是当前的时间戳

  return (

  MAGIC_NUMBER_OFFSET +

  ceiling(

  currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE,

  bucketSizeMs / UNIT_SIZE,

  )

  );

  }

  export const LOW_PRIORITY_EXPIRATION = 5000;

  export const LOW_PRIORITY_BATCH_SIZE = 250;

  // 普通异步类型

  export function computeAsyncExpiration(

  currentTime: ExpirationTime,

  ): ExpirationTime {

  return computeExpirationBucket(

  currentTime,

  LOW_PRIORITY_EXPIRATION,

  LOW_PRIORITY_BATCH_SIZE,

  );

  }

  export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;

  export const HIGH_PRIORITY_BATCH_SIZE = 100;

  // Interactive 类型

  export function computeInteractiveExpiration(currentTime: ExpirationTime) {

  return computeExpirationBucket(

  currentTime,

  HIGH_PRIORITY_EXPIRATION,

  HIGH_PRIORITY_BATCH_SIZE,

  );

  }

  复制代码

  看到代码可以看到两种类型的 Expiration Time 一种是 普通异步的 一种是 Interactive 类型 Interactive 比如说是由事件触发的,那么他的响应优先级会比较高 因为涉及到交互。

  举例&核心内容

  我们随便拿一个类型举例 computeExpirationBucket 中传入 currentTime 5000 250 这里涉及到一个方法 ceiling 可以理解成取整的方法 最终可以得到 ((((currentTime - 2 + 5000 / 10) / 25) | 0) + 1) * 25 其中 25 是 250 / 10, | 0 是取整的作用

  公式的含义是什么呢? 前面 currentTime - 2 + 5000 / 10 这部分是相对固定的内容 等于说是当前时间 + 498

  然后 ➗ 25 取整 然后 ➕ 1 再 × 5

  最后就是 (当前时间 + 498)➗ 25 取整 然后 ➕ 1 再 × 5

  当前时间加上498然后处以25取整再加1再乘以 5,需要注意的是这里的currentTime是经过msToExpirationTime处理的,也就是((now / 10) | 0) + 2,所以这里的减去2可以无视,而除以 10 取整应该是要抹平 10 毫秒内的误差,当然最终要用来计算时间差的时候会调用 expirationTimeToMs 恢复回去,但是被取整去掉的 10 毫秒误差肯定是回不去的

  简单来说在这里,最终结果是以25为单位向上增加的,比如说我们输入10002 - 10026之间,最终得到的结果都是10525,但是到了10027的到的结果就是10550,这就是除以25取整的效果。

  另外一个要提的就是msToExpirationTime和expirationTimeToMs方法,他们是想换转换的关系。这里需要注意有一点非常重要,那就是用来计算expirationTime的currentTime是通过msToExpirationTime(now)得到的,也就是预先处理过的,先处以10再加了2 这里的 2 是 magicNumberOffset,所以后面计算expirationTime要减去2 就可以理解了

  单元概念

  export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;

  export const HIGH_PRIORITY_BATCH_SIZE = 100;

  export const LOW_PRIORITY_EXPIRATION = 5000;

  export const LOW_PRIORITY_BATCH_SIZE = 250;

  复制代码

  上面提到的 25 就是一个 时间单元 在这个时间单元内计算出来的 Expiration-Time 都是一样的,React是 为了在同一个时间单元内更新的内容都是用相同的 Expiration-Time 这样更新会被合并(后面有机会可以分享) 假设如果没有单元概念的话,这样每次调用创建更新,都没有优先级顺序,这样就会浪费性能,影响效率了。 这样 Expiration-Time 就有了优先级,方便后续调度更新。

  小结

  React 这么设计抹相当于抹平了25ms内计算过期时间的误差,这样做的目的是为了非常详尽的两次更新得到相同的 expirationTime, ,然后在一次更新中完成,相当于一个自动的batchedUpdates 批量更新。以上是 expirationTime的计算方法。

posted @   qingyujie  阅读(149)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示