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的计算方法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)