expirationTime 的计算方式
- 先看expirationTime相关的源代码,这里是异步的计算方式,它会有一个过期时间
- 异步任务优先级比较低,可以被打断,防止一直被打断导致不能执行,所以React给它设置了 expirationTime 过期时间
- 也就是在这个时间之前,都可以打断,但是如果某个时间点发现任务已经过期了,还没有被执行,则强制执行该任务
- 在
ReactDOM.render
当中,它计算 expirationTime 的地方-
在 ReactFiberReconciler.js 中的
updateContainer
函数中,通过computeExpirationForFiber
方法来计算一个过期时间jsconst current = container.current; // 参数2 const currentTime = requestCurrentTime(); // 参数1 我们可以近似理解为: 当前时间到js加载完成的时间的时间差值即可 const expirationTime = computeExpirationForFiber(currentTime, current);
-
而
requestCurrentTime
这个函数,来自于 ReactFiberScheduler.js 中jsfunction requestCurrentTime() { // 这里,我把官方注释移除 // 这里表示 已经进入到 渲染阶段 了,在 ReactDOM.render 中这里不会匹配,会跳过 // 在一次render中,如果我有一个新的任务进来了,要计算 expirationTime 发现现在处于渲染阶段,这时直接返回上次 render 开始的时间,再去计算 expirationTime // 好处是 前后两次计算出来的 expirationTime 是一样的,让这个任务提前进行调度 if (isRendering) { // We're already rendering. Return the most recently read time. return currentSchedulerTime; } // Check if there's pending work. findHighestPriorityRoot(); // 刚初始化的时候,这个条件是成立的 if ( nextFlushedExpirationTime === NoWork || nextFlushedExpirationTime === Never ) { // If there's no pending work, or if the pending work is offscreen, we can // read the current time without risk of tearing. recomputeCurrentRendererTime(); currentSchedulerTime = currentRendererTime; // 两个常量划等号 return currentSchedulerTime; } // 这里,我把官方注释移除 return currentSchedulerTime; }
-
findHighestPriorityRoot
方法涉及到从调度队列中找到权限最高的 Root- 这个源码比较多,不做扩展
-
recomputeCurrentRendererTime
每一次做计算都是从当前到js加载完成后的时间间隔,再经过一些计算得到的值,jsfunction recomputeCurrentRendererTime() { const currentTimeMs = now() - originalStartTimeMs; // 当前时间 - react buddle加载完成之后初始的时间,也就是从js加载完成到现在的时间间隔 currentRendererTime = msToExpirationTime(currentTimeMs); // 计算出 currentRendererTime } // ReactFiberExpirationTime.js 这个函数得到一个时间戳 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; // UNIT_SIZE 是固定的 10, | 0 是取整的意思, MAGIC_NUMBER_OFFSET 是固定的 2 }
-
-
关于
expirationTime
的计算函数computeExpirationForFiber
有一个计算公式 -
在这个计算时间中,不需要考虑调度,只考虑计算公式,在 ReactFiberExpirationTime.js 中
jsfunction ceiling(num: number, precision: number): number { return (((num / precision) | 0) + 1) * precision; } function computeExpirationBucket( currentTime, expirationInMs, bucketSizeMs, ): ExpirationTime { return ( MAGIC_NUMBER_OFFSET + ceiling( currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE, ) ); } // 低权限计算 export function computeAsyncExpiration( currentTime: ExpirationTime, ): ExpirationTime { return computeExpirationBucket( currentTime, LOW_PRIORITY_EXPIRATION, // 5000 LOW_PRIORITY_BATCH_SIZE, // 250 ); } // 高权限计算 export function computeInteractiveExpiration(currentTime: ExpirationTime) { return computeExpirationBucket( currentTime, HIGH_PRIORITY_EXPIRATION, // 500/150 前 DEV, 后 PROD HIGH_PRIORITY_BATCH_SIZE, // 100 ); }
- 上面两个 export 方法,都是调用
computeExpirationBucket
方法来计算的 - 两个方法的区别在于 后面第2和第3个参数是不一样的
- 最终得到的公式是:
((((currentTime - 2 + 5000 / 10) / 25) | 0 ) + 1) * 25
- 其中 25 = 250 / 10,上述公式中的25,也可能是 10 (100 / 10)
- 最终计算出来的
expirationTime
是以bucketSize / UNIT_SIZE
这个单元向上叠加的 - 两个 不同的
expirationTime
的差距是 单元值的 倍数 - 对于
LOW_PRIORITY_BATCH_SIZE
是 以 25 为单元向上加的, 若前后差距在25以内,计算出来的差距都是一样的 - 对于
HIGH_PRIORITY_BATCH_SIZE
是 以 10 为单元向上加的,同上 - React 这么设定的原因: 在计算
expirationTime
时 - 在一个操作内多次调用
setState
, 即便前后调用差距很小,但从毫秒级别看,还是有差距的 - 如果没有提供任何一个调整空间,即便上个
setState
和 下一个setState
之间差距特别小,算出来的expirationTime
结果不一样 - 这就意味着,两次的任务优先级不一样, 会导致 React 整体更新执行多次,而导致整个应用的性能下降,这就是 设置 单元值的 用处
- 在一个差距很小的时间间隔内,算出来的
expirationTime
结果一样,则它们优先级也是一样的,而不需要进行区分 - 这个非常重要
- 最终计算出来的
| 0
表示 去余取整- 其中 (currentTime - 2 + 5000 / 10) 是一个不会变化的值,设为 x
- 后面就是 (((x / 25) | 0) + 1) * 25
- 这个公式的意义在于,新老值之间的差距在25以内,则结果相等
- 其中 25 = 250 / 10,上述公式中的25,也可能是 10 (100 / 10)
- 上面两个 export 方法,都是调用
-
- 至于
expirationTime
的作用还要结合后期更新的流程来看 expirationTime
是一个和业务无关的比较纯粹的计算过程问题,没有任何副作用