继上一遍React源码-ReactDom 我们查看了ReactDom 的render所构成的整个fiber 结构
我们重新看 legacyRenderSubtreeIntoContainer
, 我们构建完fiber之后往下走
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
// 一开始进来 container 上是肯定没有这个属性的
let root: Root = (container._reactRootContainer: any);
// 没有 root 会执行 if 中的操作
if (!root) {
// Initial mount
// 创建一个 root 出来,类型是 ReactRoot
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
// ... 这里直接省略callback, 因为我们不关注callback
unbatchedUpdates(() => {
// 大多数情况parentComponent 为null, 一遍不考虑,我们直接看else
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
// 调用的是 ReactRoot.prototype.render,我们直接看这里
root.render(children, callback);
}
});
} else {
// ... 这里直接省略callback, 因为我们不关注callback
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback);
}
}
return getPublicRootInstance(root._internalRoot);
}
React.prototype.render
ReactRoot.prototype.render = function(
children: ReactNodeList,
callback: ?() => mixed,
): Work {
// 这里指 FiberRoot
const root = this._internalRoot;
// ReactWork 的功能就是为了在组件渲染或更新后把所有传入
// ReactDom.render 中的回调函数全部执行一遍
// 我们这里callback 为null, 所以其实不用考虑这个
const work = new ReactWork();
callback = callback === undefined ? null : callback;
// 如果有 callback,就 push 进 work 中的数组
if (callback !== null) {
work.then(callback);
}
// work._onCommit 就是用于执行所有回调函数的
updateContainer(children, root, null, work._onCommit);
return work;
};
updateContainer
// packages\react-reconciler\src\ReactFiberReconciler.js
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
// 取出容器的 fiber 对象.
const current = container.current;
// 计算时间
const currentTime = requestCurrentTime();
// expirationTime 代表优先级,数字越大优先级越高
// sync 的数字是最大的,所以优先级也是最高的
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
requestCurrentTime
packages\shared\ReactFeatureFlags.js
的 enableNewScheduler = false; 所以使用的是packages\react-reconciler\src\ReactFiberScheduler.old.js
let originalStartTimeMs: number = now();
let currentRendererTime: ExpirationTime = msToExpirationTime(
originalStartTimeMs,
);
let currentSchedulerTime: ExpirationTime = currentRendererTime;
//
承接下面的currentSchedulerTime
和 currentRendererTime
function requestCurrentTime() {
// 调度程序调用requestCurrentTime来计算到期时间。
// 过期时间是通过将当前时间(开始时间)加起来得出的时间。
// 但是,如果在同一事件中安排了两次更新,即使实际时钟时间在第一次和第二次呼叫之间提前了,我们也应将它们的开始时间视为同时发生。
// 换句话说,由于到期时间决定了更新的批处理方式,因此我们希望在同一事件中发生的所有优先级相同的更新都收到相同的到期时间。。
// 我们跟踪两个不同的时间:当前的“渲染器”时间和当前的“调度器”时间。 渲染器时间可以随时更新。 它只是为了最大程度地降低通话性能。
// 但是,只有在没有待处理的工作,或者确定我们不在某个事件的中间时,才能更新调度程序时间。
if (isRendering) {
// 此时在渲染中,直接返回当前作态
return currentSchedulerTime;
}
// Check if there's pending work.
findHighestPriorityRoot();
if (
nextFlushedExpirationTime === NoWork ||
nextFlushedExpirationTime === Never
) {
// 如果没有待处理的工作,那么返回当前的渲染时间
recomputeCurrentRendererTime(); // 此函数 直接修改了currentRendererTime
currentSchedulerTime = currentRendererTime;
return currentSchedulerTime;
}
// 有待处理的时间时, 返回上一次的计时器时间
return currentSchedulerTime;
}
recomputeCurrentRendererTime
function recomputeCurrentRendererTime() {
const currentTimeMs = now() - originalStartTimeMs;
currentRendererTime = msToExpirationTime(currentTimeMs);
}
过期时间计算
- 先来看看的当前计算过期时间的公式吧
// packages\react-reconciler\src\ReactFiberExpirationTime.js
export const NoWork = 0;
export const Never = 1;
export const Sync = MAX_SIGNED_31_BIT_INT; // 1073741823
const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = MAX_SIGNED_31_BIT_INT - 1;
// ((ms / 10) | 0) 表示的是取整 2.5 | 0 = 2
// ms时间戳 到 expireationTime 的转换, 数值越大优先级越高
export function msToExpirationTime(ms: number): ExpirationTime {
return MAGIC_NUMBER_OFFSET - ((ms / UNIT_SIZE) | 0);
}
export function expirationTimeToMs(expirationTime: ExpirationTime): number {
return (MAGIC_NUMBER_OFFSET - expirationTime) * UNIT_SIZE;
}
// 我们可以简单看成 num + 1 * precision, 也就是在一个precision 的范围内
function ceiling(num: number, precision: number): number {
return (((num / precision) | 0) + 1) * precision;
}
//为了计算在某个bucket精度内的expirationTime,输入不同的expirationInMs,bucketSizeMs参数可以定义不同优先级的expirationTime
function computeExpirationBucket(
currentTime,
expirationInMs,
bucketSizeMs,
): ExpirationTime {
return (
MAGIC_NUMBER_OFFSET -
ceiling(
MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE,
bucketSizeMs / UNIT_SIZE,
)
);
}
export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;
// 计算异步事件过期时间,异步事件的优先级比较低
export function computeAsyncExpiration(
currentTime: ExpirationTime,
): ExpirationTime {
return computeExpirationBucket(
currentTime,
LOW_PRIORITY_EXPIRATION,
LOW_PRIORITY_BATCH_SIZE,
);
}
export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
export const HIGH_PRIORITY_BATCH_SIZE = 100;
// 计算交互事件过期时间,用户交互事件的优先级比较高
export function computeInteractiveExpiration(currentTime: ExpirationTime) {
return computeExpirationBucket(
currentTime,
HIGH_PRIORITY_EXPIRATION,
HIGH_PRIORITY_BATCH_SIZE,
);
}
// 根据过期时间获取当前优先级, 立即执行,还是普通优先级还是用户交互优先级
export function inferPriorityFromExpirationTime(
currentTime: ExpirationTime,
expirationTime: ExpirationTime,
): ReactPriorityLevel {
if (expirationTime === Sync) {
return ImmediatePriority;
}
if (expirationTime === Never) {
return IdlePriority;
}
const msUntil =
msToExpirationTime(expirationTime) - msToExpirationTime(currentTime);
if (msUntil <= 0) {
return ImmediatePriority;
}
if (msUntil <= HIGH_PRIORITY_EXPIRATION) {
return UserBlockingPriority;
}
if (msUntil <= LOW_PRIORITY_EXPIRATION) {
return NormalPriority;
}
return IdlePriority;
}
computeExpirationForFiber
我们再回到 updateContainer
继续执行到 computeExpirationForFiber
传的参数是当前时间还有fiber
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
let expirationTime;
// 一开始 expirationContext = Nowork
if (expirationContext !== NoWork) {
expirationTime = expirationContext;
} else if (isWorking) {
if (isCommitting) {
// 在提交阶段发生的更新应具有同步优先级
expirationTime = Sync;
} else {
// 更新阶段和渲染阶段的过期时间应该一样
expirationTime = nextRenderExpirationTime;
}
} else {
// 计算新的过期时间
if (fiber.mode & ConcurrentMode) {
if (isBatchingInteractiveUpdates) {
// 交互事件
expirationTime = computeInteractiveExpiration(currentTime);
} else {
// 异步事件,计算新的过期时间
expirationTime = computeAsyncExpiration(currentTime);
}
// 如果我们正在渲染树,请不要在已经渲染的到期时间进行更新。
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
expirationTime -= 1;
}
} else {
// 同步时间
expirationTime = Sync;
}
}
if (isBatchingInteractiveUpdates) {
// 跟踪最短的未执行互式到期时间。
if (
lowestPriorityPendingInteractiveExpirationTime === NoWork ||
expirationTime < lowestPriorityPendingInteractiveExpirationTime
) {
lowestPriorityPendingInteractiveExpirationTime = expirationTime;
}
}
return expirationTime;
}
updateContainerAtExpirationTime
export function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
callback: ?Function,
) {
// 一样是fiber
const current = container.current;
// 获取 context 并赋值,这里肯定取不到值得,因为 parentComponent 为 null
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
return scheduleRootUpdate(current, element, expirationTime, callback);
}
scheduleRootUpdate
- 下面开始调度了
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {
// 创建一个 update,就是内部有几个属性的对象
const update = createUpdate(expirationTime);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
// 我们的 callback 是null的所以直接不用考虑二楼
flushPassiveEffects();
// 把 update 入队,内部就是一些创建或者获取 queue(链表结构),然后给链表添加一个节点的操作
enqueueUpdate(current, update);
scheduleWork(current, expirationTime);
return expirationTime;
}