expirationTime 计算
这个过期时间指的是当前触发的更新的过期时间,在异步更新的时候, 会把任务放到scheduler上面 进行调度,react自己实现了scheduler,没有采用浏览器默认的requestIdleCallback;
过期时间的计算场景比较复杂,在这里就浅显的阅读一下
computeExpirationForFiber
当有更新产生,需要为当前的更新计算一个过期时间,在scheduler如果过期了,会强制执行,没有过期, 会放入到下一针进行执行;但是大多数情况下都是同步更新;开启ConcurrentMode才会根据优先级来进行计算
typescript
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
const priorityLevel = getCurrentPriorityLevel();
let expirationTime;
if ((fiber.mode & ConcurrentMode) === NoContext) {
// 这个判断表示 当前不是ConcurrentMode, 那么当前的更新都是同步执行
expirationTime = Sync;
} else {
// 否则会根据情况进行计算
switch (priorityLevel) {
// 用户交互级别的过期时间
case UserBlockingPriority:
expirationTime = computeInteractiveExpiration(currentTime);
break;
// 非用户操作级别的更新
case NormalPriority:
expirationTime = computeAsyncExpiration(currentTime);
break;
}
return expirationTime;
}
}TIP
用户交互级别产生的更新优先级更高
计算逻辑
- computeInteractiveExpiration 用户交互级别产生的更新
- computeAsyncExpiration 普通的异步更新
typescript
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
export type ExpirationTime = number;
export const NoWork = 0;
export const Never = 1;
export const Sync = MAX_SIGNED_31_BIT_INT;
const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = MAX_SIGNED_31_BIT_INT - 1;
// 1 unit of expiration time represents 10ms.
export function msToExpirationTime(ms: number): ExpirationTime {
// react里,过期时间是从Number的最大安全数开始计算
// 如果这个数字越大,说明优先级越高
// 这里的(ms / UNIT_SIZE) | 0 的作用是取整
// 连续10毫秒内的时间戳 计算得到的expiration是相同的
return MAGIC_NUMBER_OFFSET - ((ms / UNIT_SIZE) | 0);
}
export function expirationTimeToMs(expirationTime: ExpirationTime): number {
// 调用expirationTimeToMs将ExpirationTime转化成毫秒的时间戳,
// 但是被取整去掉的 10 毫秒误差肯定是回不去的
return (MAGIC_NUMBER_OFFSET - expirationTime) * UNIT_SIZE;
}
function ceiling(num: number, precision: number): number {
// 将数字number按precision的精确度,向上取整
return (((num / precision) | 0) + 1) * precision;
}
// computeAsyncExpiration & computeInteractiveExpiration 都是调用此方法
// 唯一的区别就是expirationInMs 和 bucketSizeMs 参数不同
// React 中有两种类型的ExpirationTime,一个是Interactive的,
// 另一种是普通的异步。Interactive的比如说是由事件触发的,
// 那么他的响应优先级会比较高因为涉及到交互。
// 在这里 computeInteractiveExpiration 传递的 expirationInMs 和 bucketSizeMs
// 更小,也就意味着 computeExpirationBucket返回的值更大 当前任务的优先级就越高
function computeExpirationBucket(
currentTime,
expirationInMs,
bucketSizeMs,
): ExpirationTime {
return (
MAGIC_NUMBER_OFFSET -
ceiling(
MAGIC_NUMBER_OFFSET - currentTime + 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;
export function computeInteractiveExpiration(currentTime: ExpirationTime) {
return computeExpirationBucket(
currentTime,
HIGH_PRIORITY_EXPIRATION,
HIGH_PRIORITY_BATCH_SIZE,
);
}总结
React 这么设计抹相当于抹平了25ms(低优先级) 或 10ms (高优先级)内计算过期时间的误差,那他为什么要这么做呢? 是为了让非常相近的两次更新得到相同的expirationTime,然后在一次更新中完成,相当于一个自动的batchedUpdates(批量更新)