updateContainer
function updateContainer(element, container, parentComponent, callback) {
//···
var currentTime = requestCurrentTimeForUpdate();
var expirationTime = computeExpirationForFiber(currentTime, current$1, suspenseConfig);
//···
return expirationTime;
}
requestCurrentTimeForUpdate
var NoWork = 0;
var currentEventTime = NoWork;
function requestCurrentTimeForUpdate() {
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
// We're inside React, so it's fine to read the actual time.
return msToExpirationTime(now());
} // We're not inside React, so we may be in the middle of a browser event.
if (currentEventTime !== NoWork) {
// Use the same start time for all updates until we enter React again.
return currentEventTime;
} // This is the first update since React yielded. Compute a new start time.
currentEventTime = msToExpirationTime(now());
return currentEventTime;
}
requestCurrentTimeForUpdate
方法中,currentTime 初始为 0,代表没有更新。var NoWork = 0;
var currentEventTime = NoWork;
currentEventTime = msToExpirationTime(now())
。msToExpirationTime
function msToExpirationTime(ms) {
// Always subtract from the offset so that we don't clash with the magic number for NoWork.
return MAGIC_NUMBER_OFFSET - (ms / UNIT_SIZE | 0);
}
function expirationTimeToMs(expirationTime) {
return (MAGIC_NUMBER_OFFSET - expirationTime) * UNIT_SIZE;
}
now
var initialTimeMs = Scheduler_now();//系统当前时间
var now = initialTimeMs < 10000 ? Scheduler_now : function () {
return Scheduler_now() - initialTimeMs;
};
var Scheduler_now = Scheduler.unstable_now,
如果初始时间 initialTimeMs 相当小,小于10s,则 now 为调度器 Scheduler 的 now 当前时间。否则 now 为 Scheduler 的当前时间减去初始时间 initialTimeMs。
MAGIC_NUMBER_OFFSET
var MAX_SIGNED_31_BIT_INT = 1073741823;//最大31位整数
var Sync = MAX_SIGNED_31_BIT_INT;//代表同步执行,不会被调度也不会被打断
var Batched = Sync - 1;//批量处理
var MAGIC_NUMBER_OFFSET = Batched - 1; //最大整数偏移量
由以上代码可以知道 MAGIC_NUMBER_OFFSET
为 1073741823-2 = 1073741821。
所以 currentTime 时间为 :
即:1073741821 - (now/10|0)。
function computeExpirationForFiber(currentTime, fiber, suspenseConfig) {
var mode = fiber.mode;
if ((mode & BlockingMode) === NoMode) {
return Sync;
}
var priorityLevel = getCurrentPriorityLevel();
if ((mode & ConcurrentMode) === NoMode) {
return priorityLevel === ImmediatePriority ? Sync : Batched;
}
if ((executionContext & RenderContext) !== NoContext) {
// Use whatever time we're already rendering
// TODO: Should there be a way to opt out, like with `runWithPriority`?
return renderExpirationTime$1;
}
var expirationTime;
if (suspenseConfig !== null) {
// Compute an expiration time based on the Suspense timeout.
expirationTime = computeSuspenseExpiration(currentTime, suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION);
} else {
// Compute an expiration time based on the Scheduler priority.
switch (priorityLevel) {
case ImmediatePriority:
expirationTime = Sync;
break;
case UserBlockingPriority$1:
// TODO: Rename this to computeUserBlockingExpiration
expirationTime = computeInteractiveExpiration(currentTime);
break;
case NormalPriority:
case LowPriority:
// TODO: Handle LowPriority
// TODO: Rename this to... something better.
expirationTime = computeAsyncExpiration(currentTime);
break;
case IdlePriority:
expirationTime = Idle;
break;
default:
{
{
throw Error( "Expected a valid priority level" );
}
}
}
} // If we're in the middle of rendering a tree, do not update at the same
// expiration time that is already rendering.
// TODO: We shouldn't have to do this if the update is on a different root.
// Refactor computeExpirationForFiber + scheduleUpdate so we have access to
// the root when we check for this condition.
if (workInProgressRoot !== null && expirationTime === renderExpirationTime$1) {
// This is a trick to move this update into a separate batch
expirationTime -= 1;
}
return expirationTime;
}
如果有使用 suspenseConfig,expirationTime = computeSuspenseExpiration(currentTime, suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION)
;
如果没有使用 suspenseConfig,React 中有五种类型的 ExpirationTime。
switch (priorityLevel) {
case ImmediatePriority:
expirationTime = Sync;
break;
case UserBlockingPriority$2:
// TODO: Rename this to computeUserBlockingExpiration
expirationTime = computeInteractiveExpiration(currentTime);
break;
case NormalPriority:
case LowPriority:
// TODO: Handle LowPriority
// TODO: Rename this to... something better.
expirationTime = computeAsyncExpiration(currentTime);
break;
case IdlePriority:
expirationTime = Idle;
break;
default:
(function () {
{
{
throw ReactError(Error("Expected a valid priority level"));
}
}
})();
}
expirationTime = Sync
1073741821
。expirationTime = computeInteractiveExpiration(currentTime)
var expirationTime
expirationTime = computeAsyncExpiration(currentTime)
expirationTime = Idle
次优先级computeInteractiveExpiration
和 低优先级computeAsyncExpiration(currentTime)
都是调用 computeExpirationBucket
方法。区别在于常量 expirationInMs 和 bucketSizeMs 不同。
var HIGH_PRIORITY_EXPIRATION = 500 ;
var HIGH_PRIORITY_BATCH_SIZE = 100;
function computeInteractiveExpiration(currentTime) {
return computeExpirationBucket(currentTime, HIGH_PRIORITY_EXPIRATION, HIGH_PRIORITY_BATCH_SIZE);
}
var LOW_PRIORITY_EXPIRATION = 5000;
var LOW_PRIORITY_BATCH_SIZE = 250;
function computeAsyncExpiration(currentTime) {
return computeExpirationBucket(currentTime, LOW_PRIORITY_EXPIRATION, LOW_PRIORITY_BATCH_SIZE);
}
核心代码:
var MAX_SIGNED_31_BIT_INT = 1073741823;
var Sync = MAX_SIGNED_31_BIT_INT;
var Batched = Sync - 1;
var UNIT_SIZE = 10;
var MAGIC_NUMBER_OFFSET = Batched - 1; // 1 unit of expiration time represents 10ms.
function ceiling(num, precision) {
return ((num / precision | 0) + 1) * precision;
}
function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs) {
return MAGIC_NUMBER_OFFSET - ceiling(MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE);
} // TODO: This corresponds to Scheduler's NormalPriority, not LowPriority. Update
// the names to reflect.
从以上函数中可以看出:
只有 currentTime 是变量,也就是当前的时间戳。如果是高优先级任务。
expirationInMs对应的是 HIGH_PRIORITY_EXPIRATION,也就是 5000。
bucketSizeMs 对应的是 HIGH_PRIORITY_BATCH_SIZE,也就是 250。
所以 computeExpirationBucket 函数接收的就是 currentTime、5000 和 250。
ceiling
函数:
function ceiling(num, precision) {
return ((num / precision | 0) + 1) * precision;
}
ceiling
调用:
ceiling(MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE)
ceiling
的参数结果为:
(MAGIC_NUMBER_OFFSET - currentTime + 5000 / 10, 250/ 10)
由于上面计算过 currentTime = MAGIC_NUMBER_OFFSET - (now/10|0)。
所以ceiling
的参数结果为:
(MAGIC_NUMBER_OFFSET - (MAGIC_NUMBER_OFFSET - (now/10|0))+ 5000 / 10, 250/ 10)
即最终 ceiling
的参数结果为:
((now/10|0))+ 500,25)
((num / precision | 0) + 1) * precision
ceiling 的结果公式如下:
(((now/10|0)+ 500)/25|0)+1)*25
expirationTime 最终的公式为:
MAGIC_NUMBER_OFFSET - (((now/10|0)+ 500)/25|0)+1)*25
即:1073741821-(((now/10|0)+ 500)/25|0)+1)*25。
其中:
25是250 / 10,表示以 25 为单位向上增加的,比如输入10002 ~ 10026 之间的数,最终得到的结果都是10525,但是到了 10027 得到的结果就是10550,这就是除以 25 取整的效果。
值得注意的是 msToExpirationTime 和 expirationTimeToMs 是相互转换的关系。有一点非常重要,那就是用来计算 expirationTime 的 currentTime 是通过 msToExpirationTime(now) 得到的,是进行预先处理过的。