上篇创建了 root 对象之后,继续执行:
...忽略callback的逻辑
//初始同步渲染模式
unbatchedUpdates(function () {
if (parentComponent != null) {
// root 外部加上 context 组件
root.legacy_renderSubtreeIntoContainer(parentComponent, children, callback);
} else {
// 执行这里
root.render(children, callback);
}
});
unbatchedUpdates:
function unbatchedUpdates(fn, a) {
// 正在批量更新标识 && 未批量更新标识 默认都是false
if (isBatchingUpdates && !isUnbatchingUpdates) {
// 如果正在批量更新 未批量更新设为true
isUnbatchingUpdates = true;
try {
return fn(a);
} finally {
// 最终 将未批量更新设为false
isUnbatchingUpdates = false;
}
}
return fn(a);
}
root.render(children, callback);
ReactRoot.prototype.render = function (children, callback) {
var root = this._internalRoot; //FiberRoot
var work = new ReactWork(); //功能就是为了在组件渲染或更新后把所有传入 ReactDom.render 中的回调函数全部执行一遍
callback = callback === undefined ? null : callback;
if (callback !== null) {
work.then(callback);
}
updateContainer(children, root, null, work._onCommit);
return work;
};
updateContainer(children, root, null, work._onCommit):
function updateContainer(element, container, parentComponent, callback) {
var current$$1 = container.current;
var currentTime = requestCurrentTime(); //计算当前时间
var expirationTime = computeExpirationForFiber(currentTime, current$$1);
return updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, callback);
}
这里的 current$$1 就是之前说的 FiberRoot.current —> createHostRootFiber(isConcurrent); //RootFiber
首先计算一个 currentTime 时间。
requestCurrentTime:
function requestCurrentTime() {
if (isRendering) {
//1、在生命周期方法中调用了setState 2、需要挂起任务的时候
// React 要求在一次rendering过程中,新产生的update用于计算过期时间的current必须跟目前的renderTime保持一致,同理在这个周期中所有产生的新的更新的过期时间都会保持一致!
return currentSchedulerTime;
}
findHighestPriorityRoot();
if (nextFlushedExpirationTime === NoWork || nextFlushedExpirationTime === Never) {
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
return currentSchedulerTime;
}
return currentSchedulerTime;
}
isRendering中调用的方法:
//originalStartTimeMs 是 React 应用初始化时就会生成的一个变量,值也是 performance.now()
var originalStartTimeMs = scheduler.unstable_now();
var currentRendererTime = msToExpirationTime(originalStartTimeMs);
var currentSchedulerTime = currentRendererTime;
//可以知道这是 32 位系统 V8 引擎里最大的整数。 react 用它来做 IdlePriority 的过期时间
var maxSigned31BitInt = 1073741823;
// 过期时间单元(ms)
var UNIT_SIZE = 10;
// 到期时间偏移量
var MAGIC_NUMBER_OFFSET = maxSigned31BitInt - 1;
// 1个到期时间单位是10ms
function msToExpirationTime(ms) {
// a | 0 下取整运算
return MAGIC_NUMBER_OFFSET - (ms / UNIT_SIZE | 0);
}
findHighestPriorityRoot:()后续还要继续看这个地方,预留
findHighestPriorityRoot函数主要执行两个操作, 一个是判断当前root是否还有任务,如果没有, 则从firstScheuleRoot链中移除。 一个是找出优先级最高的root和其对应的优先级并赋值给。初始渲染这里
nextFlushedRoot = highestPriorityRoot; // null
//也就是只有在当前没有等待中的更新的情况下,才会重新计算当前时间。
nextFlushedExpirationTime = highestPriorityWork; //0
接下来有个判断,也就是说在一个batched更新中,只有第一次创建更新才会重新计算时间,后面的所有更新都会复用第一次创建更新的时候的时间,这个也是为了保证在一个批量更新中产生的同类型的更新只会有相同的过期时间:
if (nextFlushedExpirationTime === NoWork || nextFlushedExpirationTime === Never) {
// 没有待处理的工作,或者待处理的工作是在屏幕外,
recomputeCurrentRendererTime();
// 上一个函数计算的值赋值到了currentRendererTime
currentSchedulerTime = currentRendererTime;
return currentSchedulerTime;
}
recomputeCurrentRendererTime:
function recomputeCurrentRendererTime() {
//originalStartTimeMs 是 React 应用初始化时就会生成的一个变量,值也是 performance.now()
//Date.now() 会受系统程序执行阻塞的影响不同,performance.now() 的时间是以恒定速率递增的,不受系统时间的影响(系统时间可被人为或软件调整)
// 得到的结果也就是现在离 React 应用初始化时经过了多少时间。
var currentTimeMs = scheduler.unstable_now() - originalStartTimeMs;
currentRendererTime = msToExpirationTime(currentTimeMs);
}
假如 originalStartTimeMs 为 2500,当前时间为 5000,那么算出来的差值就是 2500,也就是说当前距离 React 应用初始化已经过去了 2500 毫秒,最后通过公式得出的结果为:
currentRendererTime = 1073741822 - ((2500 / 10) | 0) = 1073741572
再回到 updateContainer 里面继续执行 expirationTime,调用computeExpirationForFiber:
计算fiber更新任务的最晚执行时间,进行比较后,决定是否继续做下一个任务
//计算fiber更新任务的最晚执行时间,进行比较后,决定是否继续做下一个任务
function computeExpirationForFiber(currentTime, fiber) {
var priorityLevel = scheduler.unstable_getCurrentPriorityLevel(); // 初始返回NormalPriority => 3
var expirationTime = void 0;
// 0 0b001(1) 0b000(0)
if ((fiber.mode & ConcurrentMode) === NoContext) {
// 如果处于commitRoot
//同步 Sync是常量, 值为32位系统的V8中的最大整数, Math.pow(2, 30) - 1
expirationTime = Sync;
} else if (isWorking && !isCommitting$1) {
// 处于renderRoot
expirationTime = nextRenderExpirationTime;
} else {
switch (priorityLevel) {
case scheduler.unstable_ImmediatePriority:
expirationTime = Sync;
break;
case scheduler.unstable_UserBlockingPriority:
//交互事件 优先级最高 计算交互状态的过期时间
expirationTime = computeInteractiveExpiration(currentTime);
break;
case scheduler.unstable_NormalPriority:
//异步 优先级最低 计算异步状态的过期时间
expirationTime = computeAsyncExpiration(currentTime);
break;
case scheduler.unstable_LowPriority:
case scheduler.unstable_IdlePriority:
expirationTime = Never;
break;
default:
invariant(false, 'Unknown priority level. This error is likely caused by a bug in React. Please file an issue.');
}
// 如果正在rendering tree, 不要更新过期时间
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
expirationTime -= 1;
}
}
//UserBlockingPriority是常量为2
if (priorityLevel === scheduler.unstable_UserBlockingPriority && (lowestPriorityPendingInteractiveExpirationTime === NoWork || expirationTime < lowestPriorityPendingInteractiveExpirationTime)) {
lowestPriorityPendingInteractiveExpirationTime = expirationTime;
}
return expirationTime;
}
交互事件的 computeInteractiveExpiration:
var HIGH_PRIORITY_EXPIRATION = 500;
var HIGH_PRIORITY_BATCH_SIZE = 100;
function computeInteractiveExpiration(currentTime) {
//10ms之内的误差视为同一时间
return computeExpirationBucket(currentTime, HIGH_PRIORITY_EXPIRATION, HIGH_PRIORITY_BATCH_SIZE);
}
计算异步状态的过期时间 computeAsyncExpiration:
/**
* 低优先级异步任务的两个时间间隔相差不到250ms(相当于25个单位的 到期时间)
* 的任务会被设置为同一个到期时间,交互 异步任务间隔为100ms(10个单位到期时间),
* 因此减少了一些不必要的组件渲染,并且保证交互可以及时的响应。
*/
var LOW_PRIORITY_EXPIRATION = 5000;
var LOW_PRIORITY_BATCH_SIZE = 250;
function computeAsyncExpiration(currentTime) {
//25ms之内的误差视为同一时间
return computeExpirationBucket(currentTime, LOW_PRIORITY_EXPIRATION, LOW_PRIORITY_BATCH_SIZE);
}
上面这两个都会走computeExpirationBucket 这个函数,只是参数不同。
computeExpirationBucket:
/**
* ceiling
* 抹平一段时间之内的时间差
* 在抹平的时间差内不管有多少个任务需要执行,
* 他们的过期时间都是同一个,这也算是一个性能优化,帮助渲染页面行为节流。
* ceiling(800, 300) 300为阶段 此阶段内的值会相同
* 0---300 300---600 600---900 900---1200
*/
function ceiling(num, precision) {
return ((num / precision | 0) + 1) * precision;
}
//arg1: 需要转换的当前时间 arg2: 不同优先级的异步任务对应的偏移时间ms arg3: 步进时间
// 通过第二个参数 可以划分优先级 500高优先 5000低优先
function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs) {
return MAGIC_NUMBER_OFFSET - ceiling(MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE);
}
继续回到 updateContainer 去执行 updateContainerAtExpirationTime:
/**
*
* @param {// 虚拟dom对象} element
* @param {*和fiber相关的_internalRoot} container
* @param {* } parentComponent
* @param {*计算出来的渲染优先级} expirationTime
* @param {*} callback
*/
function updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, callback) {
// TODO: If this is a nested container, this won't be the root.
var current$$1 = container.current;
// 回去最近的父祖节点context
// 如果自定义组件定义了childContextTypes则返回该组件context
var context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
return scheduleRootUpdate(current$$1, element, expirationTime, callback);
}
接下来进入 scheduleRootUpdate,下篇继续。。。