Skip to content

Commit

Permalink
render进一步解读,主要是currentTime、expirationTime、update相关概念,render的调用链请查看issue
Browse files Browse the repository at this point in the history
  • Loading branch information
CyfforPro committed May 20, 2020
1 parent 0af1847 commit ddf4443
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 12 deletions.
8 changes: 7 additions & 1 deletion packages/react-reconciler/src/ReactFiber.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,13 @@ function FiberNode(
// child 指向子fiber(这里注意仅指向第一个子fiber而已)
// sibling 指向下一个兄弟fiber,类比于链表中的next
// index 表明当前fiber的索引
// alternate 表示一个更新中的fiber
// alternate 合并版本的fiber
// 通常应用中会有两个fiber tree——old tree和workInProgress tree
// old tree是已经渲染好的dom tree对应的fiber tree
// workInProgress tree是正在执行更新中的fiber tree,还能实现中断恢复
// 两颗树之间还是相互引用的,便于共享属性
// 更新结束后,workInProgress tree就会变成old tree
// 这样的做法称为 double buffering
this.tag = tag;
this.key = key;
this.elementType = null;
Expand Down
27 changes: 27 additions & 0 deletions packages/react-reconciler/src/ReactFiberExpirationTime.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,19 +42,46 @@ const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = Batched - 1;

// 1 unit of expiration time represents 10ms.
// 过期时间的一个单位是10ms
// ——为啥要定义一个10ms的单位呢
// ————若两次调用该函数的入参ms相差10ms以内,则返回相同的值
// ————这意味着在调用层面是不关心10ms的误差的,只关心经过了几个过期时间单位
// ————能通过这个实现节流,提高性能
// 下面这个函数是用于计算1073741821与某个ms时间值/10(已经经过了几单位的过期时间)后的差值的
// 如入参2515毫秒,先转成251个expiration time,| 0是为了取整用的
// 然后继续计算1073741821 - 251并返回
//
// 这里可以理解为在单位刻度为10ms的时间轴上设了一个终点坐标MAGIC_NUMBER_OFFSET 1073741821
// 每次调用这个函数会得到ms/10后离终点还有多少个单位,这个值越小,意味着ms越大
export function msToExpirationTime(ms: number): ExpirationTime {
// 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);
}

// 与上面函数相反,用于从离终点刻度差值得到真正currentTime(当初调用now()得到的值)的
export function expirationTimeToMs(expirationTime: ExpirationTime): number {
return (MAGIC_NUMBER_OFFSET - expirationTime) * UNIT_SIZE;
}

/**
* 按精度向上取整,如1101精度10,向上取整为1110
*/
function ceiling(num: number, precision: number): number {
return (((num / precision) | 0) + 1) * precision;
}

/**
* 计算出不同优先级的到期时间
* currentTime,当前时间相比MAGIC_NUMBER_OFFSET还有几个单位
* expirationInMs,某类优先级的偏移时间
* bucketSizeMs,步进时间,抹平一定的时间差,有点节流的意思
* 假如这是一个交互任务
* 比如说currentTime的值为1000,注意这个是“真正当前时间”与终点刻度的差值
* expirationInMs为150,bucketSizeMs为100
* 设MAGIC_NUMBER_OFFSET为C
* C - ceiling(C - 1000 + 150/10, 100/10)
* 得到的是一个比1000小15左右的值,离终点更近了15个UNIT_SIZE
*/
function computeExpirationBucket(
currentTime,
expirationInMs,
Expand Down
13 changes: 11 additions & 2 deletions packages/react-reconciler/src/ReactFiberReconciler.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,9 @@ export function updateContainer(
if (__DEV__) {
onScheduleRoot(container, element);
}
// 从FiberRoot中取出RootFiber
const current = container.current;
// 拿到当前时间
const currentTime = requestCurrentTimeForUpdate();
if (__DEV__) {
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
Expand All @@ -259,6 +261,7 @@ export function updateContainer(
}
}
const suspenseConfig = requestCurrentSuspenseConfig();
// 计算过期时间expirationTime,数值越大优先级越高
const expirationTime = computeExpirationForFiber(
currentTime,
current,
Expand Down Expand Up @@ -289,9 +292,12 @@ export function updateContainer(
}
}

// 创建update对象,这个对象和setState息息相关
// 具体内部属性参见createUpdate
const update = createUpdate(expirationTime, suspenseConfig);
// Caution: React DevTools currently depends on this property
// being called "element".
// render其实也是一个更新,只不过没有setState,此时payload就给了个element
update.payload = {element};

callback = callback === undefined ? null : callback;
Expand All @@ -305,10 +311,13 @@ export function updateContainer(
);
}
}
// 更新update.callback为ReactDOM.render传入的回调
update.callback = callback;
}

// 将update插入队列中
enqueueUpdate(current, update);
// 调度相关工作
scheduleUpdateOnFiber(current, expirationTime);

return expirationTime;
Expand Down Expand Up @@ -437,7 +446,7 @@ export function findHostInstanceWithNoPortals(
return hostFiber.stateNode;
}

let shouldSuspendImpl = fiber => false;
let shouldSuspendImpl = (fiber) => false;

export function shouldSuspend(fiber: Fiber): boolean {
return shouldSuspendImpl(fiber);
Expand Down Expand Up @@ -516,7 +525,7 @@ if (__DEV__) {
scheduleUpdateOnFiber(fiber, Sync);
};

setSuspenseHandler = (newShouldSuspendImpl: Fiber => boolean) => {
setSuspenseHandler = (newShouldSuspendImpl: (Fiber) => boolean) => {
shouldSuspendImpl = newShouldSuspendImpl;
};
}
Expand Down
53 changes: 44 additions & 9 deletions packages/react-reconciler/src/ReactFiberWorkLoop.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -308,17 +308,34 @@ export function getWorkInProgressRoot(): FiberRoot | null {
return workInProgressRoot;
}

/**
* 该函数只是拿到了一个react定义的currentTime,即大概相对于MAGIC_NUMBER_OFFSET过了多久了
* 具体计算逻辑如下:
* 初始化(包括重置了currentEventTime后)、Render、Commit,return msToExpirationTime(now());
* 其他时候返回上次计算的currentEventTime
*/
export function requestCurrentTimeForUpdate() {
// executionContext最开始的值为0b000000
// RenderContext = 0b010000
// CommitContext = 0b100000
// 下面这个判断可以理解为通过executionContext控制当前是Render还是Commit阶段
// 若是其中之一个阶段,就直接return msToExpirationTime
// ReactDOM.render可以理解为初始化,并不属于Render或Commit阶段
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.
// 既不是Render也不是Commit阶段,
// 并且初始化后也没有调用performConcurrentWorkOnRoot重置currentEventTime
// 就return 先前计算出来的currentEventTime
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.
// 既不是Render也不是Commit阶段,
// 初始化或调用performConcurrentWorkOnRoot重置了currentEventTime,同第一个判断的return
currentEventTime = msToExpirationTime(now());
return currentEventTime;
}
Expand All @@ -327,30 +344,44 @@ export function getCurrentTime() {
return msToExpirationTime(now());
}

/**
* 该函数用于计算过期时间的,该值越大,优先级越高,返回值有如下几种数字类型
* Sync: 最大,优先级最高
* Batched: Sync-1,优先级第二
* renderExpirationTime: 要么为0,要么是之前已经用该函数计算出来的expirationTime
* computeInteractiveExpiration: 比renderExpirationTime小一点
* computeAsyncExpiration: 比computeInteractiveExpiration小一点
*/
export function computeExpirationForFiber(
currentTime: ExpirationTime,
fiber: Fiber,
suspenseConfig: null | SuspenseConfig,
): ExpirationTime {
const mode = fiber.mode;
if ((mode & BlockingMode) === NoMode) {
return Sync;
// BlockingMode=0010,那么只要mode的倒数第二位为0,就认为属于BlockingMode类别
return Sync; // Sync,同步,数值最大,优先级最高
}

// 获取当前调度的优先级,调度优先级分类见Scheduler.js
const priorityLevel = getCurrentPriorityLevel();
if ((mode & ConcurrentMode) === NoMode) {
// ConcurrentMode=0100,那么只要mode的倒数第三位为0,就认为属于ConcurrentMode类别
// Batched = Sync - 1,优先级第二
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`?
// 若是Render阶段,就返回renderExpirationTime
return renderExpirationTime;
}

let expirationTime;
if (suspenseConfig !== null) {
// Compute an expiration time based on the Suspense timeout.
// Suspense组件的特别的expirationTime,后面再研究
expirationTime = computeSuspenseExpiration(
currentTime,
suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION,
Expand All @@ -363,11 +394,15 @@ export function computeExpirationForFiber(
break;
case UserBlockingPriority:
// TODO: Rename this to computeUserBlockingExpiration
// UserBlockingPriority,走交互过期时间
// 这个可以理解为是发生用户交互事件的优先级处理
expirationTime = computeInteractiveExpiration(currentTime);
break;
case NormalPriority:
case LowPriority: // TODO: Handle LowPriority
// TODO: Rename this to... something better.
// NormaliPriority和LowPriority都是这个异步过期时间
// 这个值会比交互过期时间的值还小一点
expirationTime = computeAsyncExpiration(currentTime);
break;
case IdlePriority:
Expand Down Expand Up @@ -1099,7 +1134,7 @@ function flushPendingDiscreteUpdates() {
flushSyncCallbackQueue();
}

export function batchedUpdates<A, R>(fn: A => R, a: A): R {
export function batchedUpdates<A, R>(fn: (A) => R, a: A): R {
const prevExecutionContext = executionContext;
executionContext |= BatchedContext;
try {
Expand All @@ -1113,7 +1148,7 @@ export function batchedUpdates<A, R>(fn: A => R, a: A): R {
}
}

export function batchedEventUpdates<A, R>(fn: A => R, a: A): R {
export function batchedEventUpdates<A, R>(fn: (A) => R, a: A): R {
const prevExecutionContext = executionContext;
executionContext |= EventContext;
try {
Expand Down Expand Up @@ -1163,7 +1198,7 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
}
}

export function flushSync<A, R>(fn: A => R, a: A): R {
export function flushSync<A, R>(fn: (A) => R, a: A): R {
const prevExecutionContext = executionContext;
if ((prevExecutionContext & (RenderContext | CommitContext)) !== NoContext) {
if (__DEV__) {
Expand Down Expand Up @@ -3344,7 +3379,7 @@ function scheduleInteractions(root, expirationTime, interactions) {
const pendingInteractionMap = root.pendingInteractionMap_old;
const pendingInteractions = pendingInteractionMap.get(expirationTime);
if (pendingInteractions != null) {
interactions.forEach(interaction => {
interactions.forEach((interaction) => {
if (!pendingInteractions.has(interaction)) {
// Update the pending async work count for previously unscheduled interaction.
interaction.__count++;
Expand All @@ -3356,7 +3391,7 @@ function scheduleInteractions(root, expirationTime, interactions) {
pendingInteractionMap.set(expirationTime, new Set(interactions));

// Update the pending async work count for the current interactions.
interactions.forEach(interaction => {
interactions.forEach((interaction) => {
interaction.__count++;
});
}
Expand Down Expand Up @@ -3393,7 +3428,7 @@ function startWorkOnPendingInteractions(root, expirationTime) {
root.pendingInteractionMap_old.forEach(
(scheduledInteractions, scheduledExpirationTime) => {
if (scheduledExpirationTime >= expirationTime) {
scheduledInteractions.forEach(interaction =>
scheduledInteractions.forEach((interaction) =>
interactions.add(interaction),
);
}
Expand Down Expand Up @@ -3456,7 +3491,7 @@ function finishPendingInteractions(root, committedExpirationTime) {
if (scheduledExpirationTime > earliestRemainingTimeAfterCommit) {
pendingInteractionMap.delete(scheduledExpirationTime);

scheduledInteractions.forEach(interaction => {
scheduledInteractions.forEach((interaction) => {
interaction.__count--;

if (subscriber !== null && interaction.__count === 0) {
Expand Down Expand Up @@ -3652,7 +3687,7 @@ export function act(callback: () => Thenable<mixed>): Thenable<void> {
}
});
},
err => {
(err) => {
onDone();
reject(err);
},
Expand Down
8 changes: 8 additions & 0 deletions packages/react-reconciler/src/ReactUpdateQueue.old.js
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,17 @@ export function createUpdate(
suspenseConfig,

tag: UpdateState,
// setState的第一个参数
payload: null,
// 更新完的回调,1. ReactDOM.render中的回调 2. setState的回调
callback: null,

// 用于在队列中找到下一个节点
// 因为 update 其实就是一个队列中的节点,
// 这个属性可以用于帮助我们寻找下一个 update
// 对于批量更新来说,我们可能会创建多个 update
// 因此我们需要将这些 update 串联并存储起来
// 在必要的时候拿出来用于更新 state
next: null,
};
if (__DEV__) {
Expand Down

0 comments on commit ddf4443

Please sign in to comment.