不同的 expirationTime
1 )概述
- 在React中不仅仅有异步任务
- 大部分情况下都是同步的任务,所以会有不同 expirationTime 的存在
2 )种类
- A. Sync 模式,优先级最高
- 任务创建完成之后,立马更新到真正的dom里面
- 是一个创建即更新的流程
- B. Async 模式, 异步模式
- 会有一个调度
- 包含一系列复杂的操作在里面
- 可能会中断,所以会有一个计算出来的过期时间
- 过期时间根据异步的两种情况,如前文所述
- 最低优先级的异步
- 最高优先级的异步
- C. 指定context
3 ) 源码
- 在 ReactFiberReconciler.js 中的
updateContainer
中-
调用的是
computeExpirationForFiber
- 它接收两个参数: currentTime: ExpirationTime, fiber: Fiber
-
这个方法来自于 ReactFiberScheduler.js 文件
jsfunction 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 update expirationTime = computeInteractiveExpiration(currentTime); } else { // This is an async update expirationTime = 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 update expirationTime = 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
-
下面是它的一个修改场景
jsfunction 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
这个函数对其进行修改赋值
-
还有一种场景, 往下进行搜索
jsfunction 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; // 变成了 Sync try { return fn(a, b, c, d); // 这里是 setState 操作 } finally { expirationContext = previousExpirationContext; // 最终还原 } }
syncUpdates
在 ReactDOM.js 中的flushSync
API-
flushSync: DOMRenderer.flushSync
一直溯源往上找到 ReactFiberScheduler.js 中的flushSync
-
这个就是 本源的
flushSync
js// 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
,在之前的示例中,如下jsflushSync(() => { this.setState({ num: newNum, }) })
-
上述示例就是传入 fn 回调, 内部调用 setState
-
在这种场景下
expirationTime
变成了Sync
-
-
以上两种情况 是给
expirationContext
进行赋值
-
- 2 )当
expirationTime
变成了Sync
就不符合第一种情况了,这时候往下走,匹配到了isWorking
isWorking
表示有任务正在更新- 也是基于条件指定某个值
- 这块涉及到后续任务的更新,跳过
- 3 )没有外部强制的情况下
-
if (fiber.mode & ConcurrentMode)
-
判断
fiber.mode
是否是ConcurrentMode
-
找到
ConcurrentMode
的定义处 ReactTypeOfMode.jsjsexport type TypeOfMode = number; export const NoContext = 0b000; export const ConcurrentMode = 0b001; export const StrictMode = 0b010; export const ProfileMode = 0b100;
-
可见,使用二进制方式来定义的
-
可以通过 与或 这类逻辑的操作,非常方便的组合 Mode
-
以及判断是否有某个Mode,例如
jsconst 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,就对应了 b mode |= c // 给 mode 增加 c, 这时候 mode 变成 3,也就是 0b011 mode & b // 这时候判断mode是否有b, 如果是1,则有b, 结果是1 对应b mode & c // 这时候判断mode是否有c, 如果是1,则有c, 结果是2 对应c
- 这是一种巧妙的设计方式
- 类似的,在 ReactSideEffectTags.js 中也是这么设计的
-
回到代码里,如果是 ConcurrentMode 才会调用
-
computeInteractiveExpiration
和computeAsyncExpiration
两个函数中的一个计算expirationTime
jsif (isBatchingInteractiveUpdates) { // This is an interactive update expirationTime = computeInteractiveExpiration(currentTime); } else { // This is an async update expirationTime = computeAsyncExpiration(currentTime); }
- 关于
isBatchingInteractiveUpdates
在interactiveUpdates
函数中被赋值为 true- 大部分的 React 事件产生的更新,事件中绑定回调函数,这个回调函数执行的时候
- 大部分都是在
interactiveUpdates
情况下,也就是isBatchingInteractiveUpdates
为 true 时
- 后面
expirationTime += 1;
是为了区分下一个即将进行的更新和当前正在创造的更新,防止一样,强制把当前+1
- 关于
-
如果不属于,则还是
Sync
-
- 1 )这里有一系列的判断,首先是对
-