Expiration Time 概念回顾
在上一篇 文章 中已经大概介绍了它的概念,这里再总结一下,上文提到 存在异步类型的 Expiration-Time
, 异步的任务计算 Expiration-Time
优先级通常比较低的,所以异步的任务是可以被打断的,为了防止异步的任务一直被打断没办法执行,React
发现某一个时间点,这个任务的 Expiration-Time
已经过了,但它还没被执行掉,那么 React
将会把这个任务强制执行掉
Expiration Time 种类
在 React 的调度过程中存在着非常多不同的 expirationTime
变量帮助 React
去实现在单线程环境中调度不同优先级的任务这个需求
root.expirationTime
root.nextExpirationTimeToWorkOn
root.childExpirationTime
root.earliestPendingTime & root.lastestPendingTime
root.earliestSuspendedTime & root.lastestSuspendedTime
root.lastestPingedTime
nextFlushedExpirationTime
nextLatestAbsoluteTimeoutMs
currentRendererTime
currentSchedulerTime
这里 expirationTime
涉及到的概念比较多,会在后面调度更新的中慢慢补充和回顾。这里未完待续
另外,所有节点都会具有expirationTime
和 childExpirationTime
两个属性 这个在 Fiber
结构中等够看到。 以上所有值初始都是NoWork
也就是0
,以及他们一共会有几种情况:
NoWork
,代表没有更新Sync
,代表同步执行,不会被调度也不会被打断 创建以后马上就通知React
需要更新async
模式下计算出来的过期时间,一个时间戳 异步模式会被调度,可能会被中断,根据优先级的情况进行调度,低优先级的话是500
ms作为时间单位,高优先级的话是50
ms作为时间单位- 目标
context
的情况
我们先看看计算 Expiration Time 到底是一个什么样的逻辑,我们从 ReactFiberScheduler
源码中找到 updateContainer
创建更新阶段 调用的 computeExpirationForFiber
方法切入
ExpirationContext
这里面有一个关键的判断逻辑 expirationContext
我理解是过期的上下文,默认是等于 NoWork(不进行修改的意思),那么 expirationContext
在什么情况会被修改呢? 同样是在 ReactFiberScheduler
源码中找到 下面两个方法会对 expirationContext
进行修改
- deferredUpdates
- syncUpdates
这两个实现的是 对 expirationContext
进行赋值 然后再执行方法中传入的回调方法,这里的逻辑是 通过外部去强制让更新 使用某一种 Expiration Time 这就是 ExpirationContext的作用了
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
expirationContext = Sync;
try {
return fn(a, b, c, d);
} finally {
expirationContext = previousExpirationContext;
}
}
computeExpirationForFiber 方法解析
computeExpirationForFiber
方法源码和 代码注释
// 方法中传入 当前的时间 和 Fiber 节点
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
let expirationTime;
// 这里 expirationContext 我理解是过期的上下文,默认是等于 NoWork(不进行修改的意思)
// 那么 expirationContext
if (expirationContext !== NoWork) {
// 直接将 expirationContext 赋值给 expirationTime
expirationTime = expirationContext;
} else if (isWorking) { // 判断 是否 任务是否正在更新
// 这里涉及到 后续 commit 阶段的逻辑 这里不展开说
if (isCommitting) {
// 在提交阶段发生的更新应具有同步优先级
expirationTime = Sync; // 所以这里的 Expiration Time 是 同步的值
} else {
// 渲染阶段的更新应与正在渲染的更新 同时过期
expirationTime = nextRenderExpirationTime;
}
} else {
// 未设置显式 过期上下文,并且React 当前未在执行工作。计算新的过期时间
// 详细看 后面的 ConcurrentMode 概念
if (fiber.mode & ConcurrentMode) {
// isBatchingInteractiveUpdates 这个值 通常是在事件交互的时候 会发生更变成 True 所以一般 true 就是 interactive 类型的 Expiration Time
if (isBatchingInteractiveUpdates) {
// interactive 类型的 Expiration Time 这里的源码在上一篇文章
expirationTime = computeInteractiveExpiration(currentTime);
} else {
// async 类型的 Expiration Time 这里的源码在上一篇文章
expirationTime = computeAsyncExpiration(currentTime);
}
// 如果 正在渲染一棵树,请不要同时更新
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
// 强行改变不要同时更新
expirationTime -= 1;
}
} else {
// 异步更新
expirationTime = Sync;
}
}
if (isBatchingInteractiveUpdates) {
// 这是一个交互式更新。跟踪最低的待处理 交互式过期时间。在需要更新所有互动更新时候 React 能够同步刷新
if (
lowestPriorityPendingInteractiveExpirationTime === NoWork ||
expirationTime < lowestPriorityPendingInteractiveExpirationTime
) {
lowestPriorityPendingInteractiveExpirationTime = expirationTime;
}
}
// 最后返回时间
return expirationTime;
}
ConcurrentMode 概念
重点概念
使用二进制的方式定义变量,这样又有什么意义呢? 其实是为了方便 通过位运算实现组合 Mode
和 判断是否有 某一个 Mode
,简单来说目的就是 组合 Mode
和 判断是否存在 Mode
这个设计模式很多在 React
中有用到,所以在这里提出来
export const NoContext = 0b000;
export const ConcurrentMode = 0b001;
export const StrictMode = 0b010;
export const ProfileMode = 0b100;
举例
var a = 0b000;
var b = 0b001;
var c = 0b010;
var d = 0b100;
var mode = a; // NoContext
// 判断是否存在 b
mode & b; // 0
// 添加 b 属性到 mode
mode |= b; // 1 这个时候 mode 就是 1了
// 添加 c 属性
mode |= c; // 3
mode & b; // 1 已经包含 1 了
小结
本节内容 将整体的内容分类好,用 computeExpirationForFiber
作为切入点 计算各种情况下 Expiration Time
的值
引入了 ExpirationContext
上下文的值 作为通过外部的方式更新 Expiration Time
, 还有 ConcurrentMode
一个 React 常用的 设计模式。这次分享的内容就结束啦,谢谢大家阅读,后面会一步步更新各种类型的 Expiration Time
和 React
调度的知识