不同的 expirationTime
1 )概述
- 在React中不仅仅有异步任务
- 大部分情况下都是同步的任务,所以会有不同 expirationTime 的存在
2 )种类
- A. Sync 模式,优先级最高
- 任务创建完成之后,立马更新到真正的dom里面
- 是一个创建即更新的流程
- B. Async 模式, 异步模式
- 会有一个调度
- 包含一系列复杂的操作在里面
- 可能会中断,所以会有一个计算出来的过期时间
- 过期时间根据异步的两种情况,如前文所述
- 最低优先级的异步
- 最高优先级的异步
- C. 指定context
3 ) 源码
- 在 ReactFiberReconciler.js 中的
updateContainer
中- 调用的是
computeExpirationForFiber
- 它接收两个参数: currentTime: ExpirationTime, fiber: Fiber
- 这个方法来自于 ReactFiberScheduler.js 文件
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {let expirationTime;if (expirationContext !== NoWork) {// An explicit expiration context was set;expirationTime = expirationContext;} else if (isWorking) {if (isCommitting) {// Updates that occur during the commit phase should have sync priority// by default.expirationTime = Sync;} else {// Updates during the render phase should expire at the same time as// the work that is being rendered.expirationTime = nextRenderExpirationTime;}} else {// No explicit expiration context was set, and we're not currently// performing work. Calculate a new expiration time.if (fiber.mode & ConcurrentMode) {if (isBatchingInteractiveUpdates) {// This is an interactive updateexpirationTime = computeInteractiveExpiration(currentTime);} else {// This is an async updateexpirationTime = computeAsyncExpiration(currentTime);}// If we're in the middle of rendering a tree, do not update at the same// expiration time that is already rendering.if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {expirationTime += 1;}} else {// This is a sync updateexpirationTime = Sync;}}if (isBatchingInteractiveUpdates) {// This is an interactive update. Keep track of the lowest pending// interactive expiration time. This allows us to synchronously flush// all interactive updates when needed.if (expirationTime > lowestPriorityPendingInteractiveExpirationTime) {lowestPriorityPendingInteractiveExpirationTime = expirationTime;}}return expirationTime;}
- 1 )这里有一系列的判断,首先是对
expirationContext
的判断- 它是
ExpirationTime
类型,默认初始化值是NoWork
- 下面是它的一个修改场景
function deferredUpdates<A>(fn: () => A): A {const currentTime = requestCurrentTime();const previousExpirationContext = expirationContext;const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates;expirationContext = computeAsyncExpiration(currentTime); // 这里isBatchingInteractiveUpdates = false;try {return fn();} finally {expirationContext = previousExpirationContext;isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates;}}
expirationContext = computeAsyncExpiration(currentTime);
- 可以看到
computeAsyncExpiration
这个函数对其进行修改赋值
- 还有一种场景, 往下进行搜索
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 = Sync; // 变成了 Synctry {return fn(a, b, c, d); // 这里是 setState 操作} finally {expirationContext = previousExpirationContext; // 最终还原}}
syncUpdates
在 ReactDOM.js 中的flushSync
APIflushSync: DOMRenderer.flushSync
一直溯源往上找到 ReactFiberScheduler.js 中的flushSync
- 这个就是 本源的
flushSync
// TODO: Batching should be implemented at the renderer level, not within// the reconciler.function flushSync<A, R>(fn: (a: A) => R, a: A): R {invariant(!isRendering,'flushSync was called from inside a lifecycle method. It cannot be ' +'called when React is already rendering.',);const previousIsBatchingUpdates = isBatchingUpdates;isBatchingUpdates = true;try {return syncUpdates(fn, a); // 这里} finally {isBatchingUpdates = previousIsBatchingUpdates;performSyncWork();}}
- 上述调用的是
syncUpdates
,在之前的示例中,如下flushSync(() => {this.setState({num: newNum,})})
- 上述示例就是传入 fn 回调, 内部调用 setState
- 在这种场景下
expirationTime
变成了Sync
- 以上两种情况 是给
expirationContext
进行赋值
- 它是
- 2 )当
expirationTime
变成了Sync
就不符合第一种情况了,这时候往下走,匹配到了isWorking
isWorking
表示有任务正在更新- 也是基于条件指定某个值
- 这块涉及到后续任务的更新,跳过
- 3 )没有外部强制的情况下
if (fiber.mode & ConcurrentMode)
- 判断
fiber.mode
是否是ConcurrentMode
- 找到
ConcurrentMode
的定义处 ReactTypeOfMode.jsexport type TypeOfMode = number;export const NoContext = 0b000;export const ConcurrentMode = 0b001;export const StrictMode = 0b010;export const ProfileMode = 0b100;
- 可见,使用二进制方式来定义的
- 可以通过 与或 这类逻辑的操作,非常方便的组合 Mode
- 以及判断是否有某个Mode,例如
const a = 0b000;const b = 0b001;const c = 0b010;const d = 0b100;let mode = a; // 默认等于 a, 在后续渲染时并不知道是否有更改过mode & b // 如果 结果为 0 表示没有过b这种情况mode |= b // 这样给 mode 增加 b,这时候 mode 变成1,就对应了 bmode |= c // 给 mode 增加 c, 这时候 mode 变成 3,也就是 0b011mode & b // 这时候判断mode是否有b, 如果是1,则有b, 结果是1 对应bmode & c // 这时候判断mode是否有c, 如果是1,则有c, 结果是2 对应c
- 这是一种巧妙的设计方式
- 类似的,在 ReactSideEffectTags.js 中也是这么设计的
- 回到代码里,如果是 ConcurrentMode 才会调用
computeInteractiveExpiration
和computeAsyncExpiration
两个函数中的一个计算expirationTime
if (isBatchingInteractiveUpdates) {// This is an interactive updateexpirationTime = computeInteractiveExpiration(currentTime);} else {// This is an async updateexpirationTime = computeAsyncExpiration(currentTime);}
- 关于
isBatchingInteractiveUpdates
在interactiveUpdates
函数中被赋值为 true- 大部分的 React 事件产生的更新,事件中绑定回调函数,这个回调函数执行的时候
- 大部分都是在
interactiveUpdates
情况下,也就是isBatchingInteractiveUpdates
为 true 时
- 后面
expirationTime += 1;
是为了区分下一个即将进行的更新和当前正在创造的更新,防止一样,强制把当前+1
- 关于
- 如果不属于,则还是
Sync
- 1 )这里有一系列的判断,首先是对
- 调用的是