requestCurrentTime
1 )概述
- 关于 currentTime,在计算 expirationTime 和其他的一些地方都会用到
- 从它的名义上来讲,应等于performance.now() 或者 Date.now() 就是指定的当前时间
- 在react整体设计当中,它是有一些特定的用处和一些特殊的设定的
- 比如说在一次渲染中产生的更新,需要使用相同的时间
- 在一次批处理的更新中,应该得到相同的时间
- 还有就是挂起的任务用于记录的时候,应该也是相同的
2 )源码
js
function requestCurrentTime() {
// requestCurrentTime is called by the scheduler to compute an expiration
// time.
//
// Expiration times are computed by adding to the current time (the start
// time). However, if two updates are scheduled within the same event, we
// should treat their start times as simultaneous, even if the actual clock
// time has advanced between the first and second call.
// In other words, because expiration times determine how updates are batched,
// we want all updates of like priority that occur within the same event to
// receive the same expiration time. Otherwise we get tearing.
//
// We keep track of two separate times: the current "renderer" time and the
// current "scheduler" time. The renderer time can be updated whenever; it
// only exists to minimize the calls performance.now.
//
// But the scheduler time can only be updated if there's no pending work, or
// if we know for certain that we're not in the middle of an event.
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;
}
// There's already pending work. We might be in the middle of a browser
// event. If we were to read the current time, it could cause multiple updates
// within the same event to receive different expiration times, leading to
// tearing. Return the last read time. During the next idle callback, the
// time will be updated.
return currentSchedulerTime;
}
- 第一种情况,在
isRendering
的时候,会直接返回currentSchedulerTime
, 这个 schedulerTime- 而
isRendering
只有在performWorkOnRoot
的时候才被设置为 true - 而 performWorkOnRoot 是在 performWork 中被循环调用的
- performWorkOnRoot 的前后,都会重设
currentSchedulerTime
- 这样,在 performWorkOnRoot 的时候, isRendering 被设定为 true,并且是一个同步的方法
- 使用 performWorkOnRoot 的时候, 后期会调用 render 和 commit 两个阶段
- 在上述两个阶段里,都有可能调用组件的生命周期方法,在这里有可能产生更新
- react的设定是,在当前渲染流程中,如果在生命周期方法里触发了新的更新
- 那么它计算 expirationTime 的时间,需要一个固定的时间,所以统一返回
currentSchedulerTime
- 这个
currentSchedulerTime
就是在调用 performWorkOnRoot 之前算出来的时间 - requestCurrentTime 的
if (isRendering) return currentScheudlerTime
的设定
- 而
- 第二种情况
- 调用
findHighestPriorityRoot
找到调度队列中,优先级最高的任务 - 如果符合
if (nextFlushedExpirationTime === NoWork || nextFlushedExpirationTime === Never)
- 目前没有任务进行或正在更新的组件是从未展现的组件
- 这时候,重新计算 renderTime
recomputeCurrentRendererTime();
- 并且赋值
currentSchedulerTime = currentRendererTime;
- 最终 return currentSchedulerTime
- 这里和
batchedUpdates
里面类似- 创建了 事件的回调,多次调用 setState 会创建多个更新
- 计算多次 expirationTime
- 如果没有做这类处理
requestCurrentTime
都去计算一个时间 - 就会导致返回的时间不一致,因为时间不一致,导致计算出来的 expirationTime不一致
- 那么就导致任务优先级不一致,它们会分批次的进行更新,就会导致问题
- 在异步的模式下,即便只有在最后一次,回调调用完之后才会去调用 performWork
- 但是因为任务优先级不同,会导致分多次进行调用
- 所以,通过上述判断来规避此类问题
- 第一次调用 setState 之后,就在一个root上创建一个更新
- 从 firstScheduledRoot 到 lastScheduledRoot 里面至少会有一个
- 即将要执行的更新,在有一个的情况下,上述 if 就不满足了,就不会直接计算时间
- 直接返回 currentSchedulerTime 这个已保存的时间
- 调用
expirationTime
1 ) 概述
- 在计算 expirationTime 之前,我们计算的时间是预先处理过的
- 在 requestCurrentTime 函数中有一个
recomputeCurrentRendererTime
2 )源码
js
// /packages/react-reconciler/src/ReactFiberScheduler.js
function recomputeCurrentRendererTime() {
const currentTimeMs = now() - originalStartTimeMs;
currentRendererTime = msToExpirationTime(currentTimeMs);
}
// packages/react-reconciler/src/ReactFiberExpirationTime.js
// 1 unit of expiration time represents 10ms.
export function msToExpirationTime(ms: number): ExpirationTime {
// Always add an offset so that we don't clash with the magic number for NoWork.
// ms / UNIT_SIZE 为了防止 10ms 之内的误差,两个时间差距在10ms以内,两个时间看做一致,最终计算出来的优先级也是一致
return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET; // UNIT_SIZE 是 10, const MAGIC_NUMBER_OFFSET 是 2
}
function computeExpirationBucket(
currentTime,
expirationInMs,
bucketSizeMs,
): ExpirationTime {
return (
MAGIC_NUMBER_OFFSET +
ceiling(
currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE,
bucketSizeMs / UNIT_SIZE,
)
);
}
export function expirationTimeToMs(expirationTime: ExpirationTime): number {
return (expirationTime - MAGIC_NUMBER_OFFSET) * UNIT_SIZE;
}
- 在后续 computeExpirationBucket 中
currentTime - MAGIC_NUMBER_OFFSET
- 所以 MAGIC_NUMBER_OFFSET 没有影响到 真正时间的计算
- 误差在10ms以内的,前后两个用于计算 expirationTime 的 currentTime
- 它们的误差会被抹平,就是 msToExpirationTime 这个方法被设计的意义
- 最终使用具体的时间设置timeout, 判断是否过期的时候,会通过
expirationTimeToMs
把这个时间转回来