之前知道了首次渲染创建更新的过程,现在进入调度更新的过程(下图中的右侧部分,从scheduleUpdateOnFiber开始进入调度阶段)。

graph TD;

  render --> legacyRenderSubtreeIntoContainer;
  legacyRenderSubtreeIntoContainer --> legacyCreateRootFromDOMContainer;
  legacyRenderSubtreeIntoContainer --> unbatchedUpdates;
  unbatchedUpdates --> updateContainer;
  updateContainer --> scheduleUpdateOnFiber;
  scheduleUpdateOnFiber --> checkForNestedUpdates;
  scheduleUpdateOnFiber --> markUpdateTimeFromFiberToRoot;
  markUpdateTimeFromFiberToRoot --> markRootUpdatedAtTime;
  scheduleUpdateOnFiber --> getCurrentPriorityLevel;
  scheduleUpdateOnFiber --> schedulePendingInteractions;
  schedulePendingInteractions --> scheduleInteractions;
  scheduleUpdateOnFiber --> performSyncWorkOnRoot;
  performSyncWorkOnRoot --> renderRootSync;
  renderRootSync --> pushDispatcher;
  renderRootSync --> prepareFreshStack;
  prepareFreshStack --> createWorkInProgress;
  renderRootSync --> startWorkOnPendingInteractions;
  startWorkOnPendingInteractions --> scheduleInteractions;
  renderRootSync --> pushInteractions;
  renderRootSync --> workLoopSync;
函数名 参数(类型) 位置
scheduleUpdateOnFiber fiber(Fiber)、expirationTime(ExpirationTime) react-reconciler/src/ReactFiberWorkLoop
checkForNestedUpdates - react-reconciler/src/ReactFiberWorkLoop
markUpdateTimeFromFiberToRoot - react-reconciler/src/ReactFiberWorkLoop
markRootUpdatedAtTime fiber(Fiber)、expirationTime(ExpirationTime) react-reconciler/src/ReactFiberRoot
getCurrentPriorityLevel - react-reconciler/src/SchedulerWithReactIntegration
schedulePendingInteractions - react-reconciler/src/SchedulerWithReactIntegration
scheduleInteractions root, expirationTime, interactions react-reconciler/src/ReactFiberWorkLoop
performSyncWorkOnRoot root(FiberRoot) react-reconciler/src/ReactFiberWorkLoop
renderRootSync root(FiberRoot), expirationTime react-reconciler/src/ReactFiberWorkLoop
pushDispatcher root(FiberRoot), expirationTime react-reconciler/src/ReactFiberWorkLoop
prepareFreshStack root(FiberRoot), expirationTime react-reconciler/src/ReactFiberWorkLoop
startWorkOnPendingInteractions root(FiberRoot), expirationTime react-reconciler/src/ReactFiberWorkLoop
startWorkOnPendingInteractions root(FiberRoot), expirationTime, interactions react-reconciler/src/ReactFiberWorkLoop
pushInteractions root(FiberRoot) react-reconciler/src/ReactFiberWorkLoop
workLoopSync - react-reconciler/src/ReactFiberWorkLoop
performUnitOfWork workInProgress(Fiber) react-reconciler/src/ReactFiberWorkLoop
createWorkInProgress Fiber, pendingProps react-reconciler/src/ReactFiber
pushHostRootContext Fiber react-reconciler/src/ReactFiberBeginWork
pushTopLevelContextObject Fiber, context, didChange(boolean) react-reconciler/src/ReactFiberContext
push cursor(StackCursor), value(T), fiber react-reconciler/src/ReactFiberStack
pushHostContainer fiber, dom react-reconciler/src/ReactFiberHostContext
getRootHostContext dom react-dom/src/client/ReactDOMHostConfig
getChildNamespace parentNamespace(string或null),type(string) react-dom/src/shared/DOMNamespaces
pop cursor(StackCursor), value(T), fiber react-reconciler/src/ReactFiberStack
beginWork current(Fiber 或 null), workInProgress(Fiber), renderExpirationTime(ExpirationTime) react-reconciler/src/ReactFiberBeginWork
bailoutOnAlreadyFinishedWork current(Fiber 或 null), workInProgress(Fiber), renderExpirationTime(ExpirationTime) react-reconciler/src/ReactFiberBeginWork
updateHostRoot current(Fiber 或 null), workInProgress(Fiber), renderExpirationTime(ExpirationTime) react-reconciler/src/ReactFiberBeginWork
cloneUpdateQueue current(Fiber), workInProgress(Fiber) react-reconciler/src/ReactUpdateQueue
processUpdateQueue workInProgress(Fiber), props(any), instance(any), renderExpirationTime(ExpirationTime) react-reconciler/src/ReactUpdateQueue
markRenderEventTimeAndConfig expirationTime(ExpirationTime), suspenseConfig(null或SuspenseConfig) react-reconciler/src/ReactFiberWorkLoop
getStateFromUpdate workInProgress(Fiber), queue(UpdateQueue), update(Update), prevState(State), nextProps(any), instance(any) react-reconciler/src/ReactUpdateQueue
reconcileChildren current(Fiber 或 null), workInProgress(Fiber), nextChildren(any), renderExpirationTime(ExpirationTime) react-reconciler/src/ReactFiberBeginWork
reconcileChildFibers returnFiber(Fiber), currentFirstChild(Fiber或null), newChild(any), renderExpirationTime(ExpirationTime) react-reconciler/src/ReactChildFiber
placeSingleChild newFiber(Fiber) react-reconciler/src/ReactChildFiber
reconcileSingleElement returnFiber(Fiber), currentFirstChild(Fiber或null), element(ReactElement), expiration(Expiration) react-reconciler/src/ReactChildFiber
createFiberFromElement element(ReactElement), mode(TypeOfMode), expiration(Expiration) react-reconciler/src/ReactFiber
createFiberFromTypeAndProps type(ReactElementType), key(null或string), pendingProps(any), owner(null或Fiber), mode(TypeOfMode), expiration(Expiration) react-reconciler/src/ReactFiber
coerceRef returnFiber(Fiber), current(null或Fiber), element(ReactElement) react-reconciler/src/ReactFiber
updateMode current(null或Fiber), workInProgress(Fiber), renderExpirationTime(ExpirationTime) react-reconciler/src/ReactFiber
mountChildFibers returnFiber(Fiber), currentFirstChild(Fiber或null), newChild(any), renderExpirationTime(ExpirationTime) react-reconciler/src/ReactChildFiber
placeSingleChild newFiber(Fiber) react-reconciler/src/ReactChildFiber
mountIndeterminateComponent current(null或Fiber), workInProgress(Fiber), Component, renderExpirationTime react-reconciler/src/ReactFiberBeginWork
getUnmaskedContext current(null或Fiber), workInProgress(Fiber), Component, renderExpirationTime react-reconciler/src/ReactFiberContext
renderWithHooks current(null或Fiber), workInProgress(Fiber), Component, props, secondArg, nextRenderExpirationTime react-reconciler/src/ReactFiberHooks
updateHostComponent current(null或Fiber), workInProgress(Fiber), renderExpirationTime react-reconciler/src/ReactFiberBeginWork
reconcileChildrenArray returnFiber(Fiber),currentFirstChild(null或Fiber), newChildren(Array), expirationTime(ExpirationTime) react-reconciler/src/ReactChildFiber
createChild returnFiber(Fiber),newChild(any), expirationTime(ExpirationTime) react-reconciler/src/ReactChildFiber
placeChild newFiber(Fiber),lastPlacedIndex(number), newIndex(number) react-reconciler/src/ReactChildFiber
deleteRemainingChildren returnFiber(Fiber),currentFirstChild(Fiber或null) react-reconciler/src/ReactChildFiber
completeUnitOfWork unitOfWork(Fiber) react-reconciler/src/ReactFiberWorkLoop
completeWork current(Fiber或null), workInProgress(Fiber), renderExpiration(ExpirationTime) react-reconciler/src/ReactFiberCompleteWork
createInstance type(string), props(props), rootContainerInstance(Container), hostContext(HostContext), internalInstanceHandle(Object) react-dom/src/client/ReactDOMHostConfig
appendAllChildren parent(Instance), workInProgress(Fiber), needsVisibilityToggle(boolean), isHidden(boolean) react-reconciler/src/ReactFiberCompleteWork
createTextInstance text(string), rootContainerInstance(container), hostContext(HostContext), internalInstanceHandle(Object) react-reconciler/src/ReactFiberCompleteWork

主要函数

  1. scheduleUpdateOnFiber
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
// export const NoContext = /*             */ 0b0000000;
// const BatchedContext = /* */ 0b0000001;
// const EventContext = /* */ 0b0000010;
// const DiscreteEventContext = /* */ 0b0000100;
// const LegacyUnbatchedContext = /* */ 0b0001000;
// const RenderContext = /* */ 0b0010000;
// const CommitContext = /* */ 0b0100000;
export function scheduleUpdateOnFiber(
fiber: Fiber,
expirationTime: ExpirationTime,
) {
checkForNestedUpdates();
// warnAboutRenderPhaseUpdatesInDEV(fiber); // 这个函数运行在测试环境
// 首次渲染这个函数给FiberRoot的pendingTime设置了范围(firstPendingTime = MAX_SIGNED_31_BIT_INT, lastPendingTime = MAX_SIGNED_31_BIT_INT)
const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
if (root === null) {
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return;
}

// TODO: computeExpirationForFiber also reads the priority. Pass the
// priority as an argument to that function and this one.
const priorityLevel = getCurrentPriorityLevel(); // 首次渲染 NormalPriority = 3

if (expirationTime === Sync) { // 首次渲染等于Sync
// 首次渲染过程中,处理过executionContext的地方位于unbatchedUpdates中
// 经过如下步骤:
// const prevExecutionContext = executionContext;
// executionContext &= ~BatchedContext;
// executionContext |= LegacyUnbatchedContext;
// 此时的executionContext中加入了LegacyUnbatchedContext,所以值为0b0001000,也就是8
if (
// Check if we're inside unbatchedUpdates
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering
(executionContext & (RenderContext | CommitContext)) === NoContext
) { // 首次渲染进入该分支
// Register pending interactions on the root to avoid losing traced interaction data.
schedulePendingInteractions(root, expirationTime);

// This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
performSyncWorkOnRoot(root);
} else {
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
if (executionContext === NoContext) {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of legacy mode.
flushSyncCallbackQueue();
}
}
} else {
// Schedule a discrete update but only if it's not Sync.
if (
(executionContext & DiscreteEventContext) !== NoContext &&
// Only updates at user-blocking priority or greater are considered
// discrete, even inside a discrete event.
(priorityLevel === UserBlockingPriority ||
priorityLevel === ImmediatePriority)
) {
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
if (rootsWithPendingDiscreteUpdates === null) {
rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
} else {
const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
if (
lastDiscreteTime === undefined ||
lastDiscreteTime > expirationTime
) {
rootsWithPendingDiscreteUpdates.set(root, expirationTime);
}
}
}
// Schedule other updates after in case the callback is sync.
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
}
}
  1. checkForNestedUpdates
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const NESTED_UPDATE_LIMIT = 50;
let nestedUpdateCount: number = 0;

function checkForNestedUpdates() {
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
nestedUpdateCount = 0;
rootWithNestedUpdates = null;
invariant(
false,
'Maximum update depth exceeded. This can happen when a component ' +
'repeatedly calls setState inside componentWillUpdate or ' +
'componentDidUpdate. React limits the number of nested updates to ' +
'prevent infinite loops.',
);
}
}
  1. markUpdateTimeFromFiberToRoot

传入该函数的fiber值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// ====================fiber============================
{
actualDuration: 0,
actualStartTime: -1,
alternate: null,
child: null,
childExpirationTime: NoWork, // 0
dependencies_old: null,
effectTag: NoEffect, // 0
elementType: null,
expirationTime: NoWork, // 0
firstEffect: null,
index: 0,
key: null,
lastEffect: null,
memoizedProps: null,
memoizedState: null,
mode: NoMode, // 0
nextEffect: null,
pendingProps: null,
ref: null,
return: null,
setBaseDuration: 0,
sibling: null,
stateNode: container,
tag: HostRoot, // 3
treeBaseDuration: 0,
type: null,
updateQueue: {
baseState: null,
effects: null,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
},
},
}

// ======================expirationTime==================

MAX_SIGNED_31_BIT_INT, // 1073741823

// ======================FiberRoot=======================

{
callbackNode: null,
callbackPriority_old: Nopriority, // 90
containerInfo: '<div id="root">', // dom元素()
context: {},
current,
finishedExpirationTime: NoWork, // 0
finishedWork: null,
firstPendingTime: NoWork, // 0
firstSuspendedTime: NoWork, // 0
hydrate: false,
interactionThreadID: 1,
lastExpiredTime: NoWork, // 0
lastPendingTime: NoWork, // 0
lastPingedTime: NoWork, // 0
lastSuspendedTime: NoWork, // 0
memoizedInteractions: Set[],
mutableSourceEagerHydrationData: null,
mutableSourceLastPendingUpdateTime: NoWork, // 0
nextKnownPendingLevel: NoWork, // 0
pendingChildren: null,
pendingContext: null,
pendingInteractionMap_old: Map(0),
pingCache: null,
timeoutHandle: noTimeout, // -1
tag: LegacyRoot, // 0
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
let workInProgressRoot: FiberRoot | null = null;


// This is split into a separate function so we can mark a fiber with pending
// work without treating it as a typical update that originates from an event;
// e.g. retrying a Suspense boundary isn't an update, but it does schedule work
// on a fiber.
function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {
// Update the source fiber's expiration time
if (fiber.expirationTime < expirationTime) {
fiber.expirationTime = expirationTime;
}
let alternate = fiber.alternate;
if (alternate !== null && alternate.expirationTime < expirationTime) { // 首次渲染alternate是null,不会进入这个分支
alternate.expirationTime = expirationTime;
}

// Walk the parent path to the root and update the child expiration time.
let node = fiber.return; // 首次渲染,值为null
let root = null;
if (node === null && fiber.tag === HostRoot) { // 首次渲染进入该分支
root = fiber.stateNode; // 首次渲染值为FiberRoot
} else {
while (node !== null) {
alternate = node.alternate;
if (node.childExpirationTime < expirationTime) {
node.childExpirationTime = expirationTime;
if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
} else if (
alternate !== null &&
alternate.childExpirationTime < expirationTime
) {
alternate.childExpirationTime = expirationTime;
}
if (node.return === null && node.tag === HostRoot) {
root = node.stateNode;
break;
}
node = node.return;
}
}

if (root !== null) {
if (workInProgressRoot === root) { // 首次渲染 workInProgressRoot === null,不进入该分支
// Received an update to a tree that's in the middle of rendering. Mark
// that's unprocessed work on this root.
markUnprocessedUpdateTime(expirationTime);

if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
// The root already suspended with a delay, which means this render
// definitely won't finish. Since we have a new update, let's mark it as
// suspended now, right before marking the incoming update. This has the
// effect of interrupting the current render and switching to the update.
// TODO: This happens to work when receiving an update during the render
// phase, because of the trick inside computeExpirationForFiber to
// subtract 1 from `renderExpirationTime` to move it into a
// separate bucket. But we should probably model it with an exception,
// using the same mechanism we use to force hydration of a subtree.
// TODO: This does not account for low pri updates that were already
// scheduled before the root started rendering. Need to track the next
// pending expiration time (perhaps by backtracking the return path) and
// then trigger a restart in the `renderDidSuspendDelayIfPossible` path.
markRootSuspendedAtTime(root, renderExpirationTime);
}
}
// Mark that the root has a pending update.
markRootUpdatedAtTime(root, expirationTime);
}

return root;
}
  1. markRootUpdatedAtTime

首次渲染的时候上面的函数没有执行实质性的操作,该函数做的实质性操作是给FiberRoot的pending times设置了范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// Mark that the root has a pending update.

export function markRootUpdatedAtTime(
root: FiberRoot,
expirationTime: ExpirationTime,
): void {
// Update the range of pending times
const firstPendingTime = root.firstPendingTime; // 首次渲染 NoWork = 0
if (expirationTime > firstPendingTime) {
root.firstPendingTime = expirationTime; // 首次渲染 MAX_SIGNED_31_BIT_INT = 1073741823
}
const lastPendingTime = root.lastPendingTime; // 首次渲染 NoWork = 0
if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {
root.lastPendingTime = expirationTime; // 首次渲染 MAX_SIGNED_31_BIT_INT = 1073741823
}

// Update the range of suspended times. Treat everything lower priority or
// equal to this update as unsuspended.
const firstSuspendedTime = root.firstSuspendedTime; // 首次渲染 NoWork = 0
if (firstSuspendedTime !== NoWork) { // 首次渲染不会进入该分支
if (expirationTime >= firstSuspendedTime) {
// The entire suspended range is now unsuspended.
root.firstSuspendedTime = root.lastSuspendedTime = root.nextKnownPendingLevel = NoWork;
} else if (expirationTime >= root.lastSuspendedTime) {
root.lastSuspendedTime = expirationTime + 1;
}

// This is a pending level. Check if it's higher priority than the next
// known pending level.
if (expirationTime > root.nextKnownPendingLevel) {
root.nextKnownPendingLevel = expirationTime;
}
}
}
  1. getCurrentPriorityLevel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
export const NoPriority = 0;
export const ImmediatePriority = 1;
export const UserBlockingPriority = 2;
export const NormalPriority = 3;
export const LowPriority = 4;
export const IdlePriority = 5;

var currentPriorityLevel = NormalPriority;
// Scheduler_getCurrentPriorityLevel就是unstable_getCurrentPriorityLevel
function unstable_getCurrentPriorityLevel() {
return currentPriorityLevel;
}

// ImmediatePriority as unstable_ImmediatePriority,
// UserBlockingPriority as unstable_UserBlockingPriority,
// NormalPriority as unstable_NormalPriority,
// IdlePriority as unstable_IdlePriority,
// LowPriority as unstable_LowPriority,

// unstable_ImmediatePriority: Scheduler_ImmediatePriority,
// unstable_UserBlockingPriority: Scheduler_UserBlockingPriority,
// unstable_NormalPriority: Scheduler_NormalPriority,
// unstable_LowPriority: Scheduler_LowPriority,
// unstable_IdlePriority: Scheduler_IdlePriority,

function getCurrentPriorityLevel(): ReactPriorityLevel {
switch (Scheduler_getCurrentPriorityLevel()) {
case Scheduler_ImmediatePriority:
return ImmediatePriority;
case Scheduler_UserBlockingPriority:
return UserBlockingPriority;
case Scheduler_NormalPriority:
return NormalPriority;
case Scheduler_LowPriority:
return LowPriority;
case Scheduler_IdlePriority:
return IdlePriority;
default:
invariant(false, 'Unknown priority level.');
}
}

首次渲染返回的结果是NormalPriority

  1. schedulePendingInteractions
1
2
3
4
5
6
7
8
9
function schedulePendingInteractions(root, expirationTime) {
// This is called when work is scheduled on a root.
// It associates the current interactions with the newly-scheduled expiration.
// They will be restored when that expiration is later committed.
if (!enableSchedulerTracing) { // enableSchedulerTracing = true
return;
}
scheduleInteractions(root, expirationTime, __interactionsRef.current); // __interactionsRef.current = Set[]
}

代码中的__interactionsRef初始值是null,但何时更改了新值完全没发现。。。最一开始调用createElement的时候就已经是此处看到的值了。。。

  1. scheduleInteractions

首次渲染,不进入任何分支

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function scheduleInteractions(root, expirationTime, interactions) {
if (!enableSchedulerTracing) {
return;
}

if (interactions.size > 0) {
const pendingInteractionMap = root.pendingInteractionMap_old;
const pendingInteractions = pendingInteractionMap.get(expirationTime);
if (pendingInteractions != null) {
interactions.forEach(interaction => {
if (!pendingInteractions.has(interaction)) {
// Update the pending async work count for previously unscheduled interaction.
interaction.__count++;
}

pendingInteractions.add(interaction);
});
} else {
pendingInteractionMap.set(expirationTime, new Set(interactions));

// Update the pending async work count for the current interactions.
interactions.forEach(interaction => {
interaction.__count++;
});
}

const subscriber = __subscriberRef.current;
if (subscriber !== null) {
const threadID = computeThreadID(root, expirationTime);
subscriber.onWorkScheduled(interactions, threadID);
}
}
}
  1. performSyncWorkOnRoot

首次渲染走同步流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// This is the entry point for synchronous tasks that don't go
// through Scheduler
function performSynliuchengcWorkOnRoot(root) {
invariant(
(executionContext & (RenderContext | CommitContext)) === NoContext,
'Should not already be working.',
);

flushPassiveEffects(); // 首次渲染内部不会进入分支,直接跳出

const lastExpiredTime = root.lastExpiredTime; // 首次渲染NoWork

let expirationTime;
if (lastExpiredTime !== NoWork) {
// There's expired work on this root. Check if we have a partial tree
// that we can reuse.
if (
root === workInProgressRoot &&
renderExpirationTime >= lastExpiredTime
) {
// There's a partial tree with equal or greater than priority than the
// expired level. Finish rendering it before rendering the rest of the
// expired work.
expirationTime = renderExpirationTime;
} else {
// Start a fresh tree.
expirationTime = lastExpiredTime;
}
} else {
// There's no expired work. This must be a new, synchronous render.
expirationTime = Sync; // 首次渲染 MAX_SIGNED_31_BIT_INT 1073741823
}

let exitStatus = renderRootSync(root, expirationTime);

if (root.tag !== LegacyRoot && exitStatus === RootErrored) {
executionContext |= RetryAfterError;

// If an error occurred during hydration,
// discard server response and fall back to client side render.
if (root.hydrate) {
root.hydrate = false;
clearContainer(root.containerInfo);
}

// If something threw an error, try rendering one more time. We'll
// render synchronously to block concurrent data mutations, and we'll
// render at Idle (or lower) so that all pending updates are included.
// If it still fails after the second attempt, we'll give up and commit
// the resulting tree.
expirationTime = expirationTime > Idle ? Idle : expirationTime;
exitStatus = renderRootSync(root, expirationTime);
}

if (exitStatus === RootFatalErrored) {
const fatalError = workInProgressRootFatalError;
prepareFreshStack(root, expirationTime);
markRootSuspendedAtTime(root, expirationTime);
ensureRootIsScheduled(root);
throw fatalError;
}

// We now have a consistent tree. Because this is a sync render, we
// will commit it even if something suspended.
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedExpirationTime = expirationTime;
root.nextKnownPendingLevel = getRemainingExpirationTime(finishedWork);
commitRoot(root);

// Before exiting, make sure there's a callback scheduled for the next
// pending level.
ensureRootIsScheduled(root);

return null;
}
  1. renderRootSync
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 首次渲染过程中,处理过executionContext的地方位于unbatchedUpdates中
// 经过如下步骤:
// const prevExecutionContext = executionContext;
// executionContext &= ~BatchedContext;
// executionContext |= LegacyUnbatchedContext;
// 此时的executionContext中加入了LegacyUnbatchedContext,所以值为0b0001000,也就是8

// const RenderContext = 0b0010000;
// let renderExpirationTime: ExpirationTime = NoWork;

function renderRootSync(root, expirationTime) {
const prevExecutionContext = executionContext; // LegacyUnbatchedContext = 0b0001000
executionContext |= RenderContext; // RenderContext | LegacyUnbatchedContext = 0b0011000
const prevDispatcher = pushDispatcher(root); // 首次渲染此处的返回值见下方

// 每太明白下面的注释
// If the root or expiration time have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) { // 首次渲染会进入该分支
prepareFreshStack(root, expirationTime);
startWorkOnPendingInteractions(root, expirationTime);
}

const prevInteractions = pushInteractions(root);

do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
resetContextDependencies();
if (enableSchedulerTracing) {
popInteractions(((prevInteractions: any): Set<Interaction>));
}

executionContext = prevExecutionContext;
popDispatcher(prevDispatcher);

if (workInProgress !== null) {
// This is a sync render, so we should have finished the whole tree.
invariant(
false,
'Cannot commit an incomplete root. This error is likely caused by a ' +
'bug in React. Please file an issue.',
);
}

// Set this to null to indicate there's no in-progress render.
workInProgressRoot = null;

return workInProgressRootExitStatus;
}
  1. pushDispatcher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
* Keeps track of the current dispatcher.
*/
const ReactCurrentDispatcher = {
current: (null: null | Dispatcher),
};

const ContextOnlyDispatcher = {
readContext,
useCallback: throwInvalidHookError,
useContext: throwInvalidHookError,
useEffect: throwInvalidHookError,
useImperativeHandle: throwInvalidHookError,
useLayoutEffect: throwInvalidHookError,
useMemo: throwInvalidHookError,
useReducer: throwInvalidHookError,
useRef: throwInvalidHookError,
useState: throwInvalidHookError,
useDebugValue: throwInvalidHookError,
useResponder: throwInvalidHookError,
useDeferredValue: throwInvalidHookError,
useTransition: throwInvalidHookError,
useMutableSource: throwInvalidHookError,
useOpaqueIdentifier: throwInvalidHookError,

unstable_isNewReconciler: enableNewReconciler,
};

function pushDispatcher(root) {
const prevDispatcher = ReactCurrentDispatcher.current;
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
if (prevDispatcher === null) {
// The React isomorphic package does not include a default dispatcher.
// Instead the first renderer will lazily attach one, in order to give
// nicer error messages.
return ContextOnlyDispatcher; // 这里返回ContextOnlyDispatcher(感觉像是和Hook相关的)
} else {
return prevDispatcher;
}
}
  1. prepareFreshStack
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
// export const noTimeout = -1;

// // The root we're working on
// let workInProgressRoot: FiberRoot | null = null;

// // The fiber we're working on
// let workInProgress: Fiber | null = null;

// const RootIncomplete = 0;
// // Whether to root completed, errored, suspended, etc.
// let workInProgressRootExitStatus: RootExitStatus = RootIncomplete;

// // A fatal error, if one is thrown
// let workInProgressRootFatalError: mixed = null;

// // Most recent event time among processed updates during this render.
// // This is conceptually a time stamp but expressed in terms of an ExpirationTime
// // because we deal mostly with expiration times in the hot path, so this avoids
// // the conversion happening in the hot path.
// let workInProgressRootLatestProcessedExpirationTime: ExpirationTime = Sync;
// let workInProgressRootLatestSuspenseTimeout: ExpirationTime = Sync;
// let workInProgressRootCanSuspendUsingConfig: null | SuspenseConfig = null;

// // The work left over by components that were visited during this render. Only
// // includes unprocessed updates, not work in bailed out children.
// let workInProgressRootNextUnprocessedUpdateTime: ExpirationTime = NoWork;

// // If we're pinged while rendering we don't always restart immediately.
// // This flag determines if it might be worthwhile to restart if an opportunity
// // happens latere.
// let workInProgressRootHasPendingPing: boolean = false;

// // Marks the need to reschedule pending interactions at these expiration times
// // during the commit phase. This enables them to be traced across components
// // that spawn new work during render. E.g. hidden boundaries, suspended SSR
// // hydration or SuspenseList.
// let spawnedWorkDuringRender: null | Array<ExpirationTime> = null;

function prepareFreshStack(root, expirationTime) {
root.finishedWork = null;
root.finishedExpirationTime = NoWork;

const timeoutHandle = root.timeoutHandle; // 首次渲染时 -1
if (timeoutHandle !== noTimeout) { // 首次渲染不进入该分支
// The root previous suspended and scheduled a timeout to commit a fallback
// state. Now that we have additional work, cancel the timeout.
root.timeoutHandle = noTimeout;
// $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
cancelTimeout(timeoutHandle);
}

// Check if there's a suspended level at lower priority.
const lastSuspendedTime = root.lastSuspendedTime; // 0
if (lastSuspendedTime !== NoWork && lastSuspendedTime < expirationTime) { // 首次渲染不进入该分支
const lastPingedTime = root.lastPingedTime;
// Make sure the suspended level is marked as pinged so that we return back
// to it later, in case the render we're about to start gets aborted.
// Generally we only reach this path via a ping, but we shouldn't assume
// that will always be the case.
// Note: This is defensive coding to prevent a pending commit from
// being dropped without being rescheduled. It shouldn't be necessary.
if (lastPingedTime === NoWork || lastPingedTime > lastSuspendedTime) {
root.lastPingedTime = lastSuspendedTime;
}
}

if (workInProgress !== null) { // 首次渲染不进入该分支
let interruptedWork = workInProgress.return;
while (interruptedWork !== null) {
unwindInterruptedWork(interruptedWork);
interruptedWork = interruptedWork.return;
}
}
workInProgressRoot = root;
workInProgress = createWorkInProgress(root.current, null);
renderExpirationTime = expirationTime;
workInProgressRootExitStatus = RootIncomplete;
workInProgressRootFatalError = null;
workInProgressRootLatestProcessedExpirationTime = Sync;
workInProgressRootLatestSuspenseTimeout = Sync;
workInProgressRootCanSuspendUsingConfig = null;
workInProgressRootNextUnprocessedUpdateTime = NoWork;
workInProgressRootHasPendingPing = false;

if (enableSchedulerTracing) {
spawnedWorkDuringRender = null;
}
}
  1. createWorkInProgress

首次渲染时的调用:createWorkInProgress(root.current, null);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// 此时的current
{
actualDuration: 0,
actualStartTime: -1,
alternate: null,
child: null,
childExpirationTime: NoWork, // 0
dependencies_old: null,
effectTag: NoEffect, // 0
elementType: null,
expirationTime: NoWork, // 0
firstEffect: null,
index: 0,
key: null,
lastEffect: null,
memoizedProps: null,
memoizedState: null,
mode: NoMode, // 0
nextEffect: null,
pendingProps: null,
ref: null,
return: null,
setBaseDuration: 0,
sibling: null,
stateNode: container,
tag: HostRoot, // 3
treeBaseDuration: 0,
type: null,
updateQueue: {
baseState: null,
effects: null,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
},
},
}
// This is used to create an alternate fiber to do work on.
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
let workInProgress = current.alternate; // 首次渲染 null
if (workInProgress === null) {
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
workInProgress = createFiber(
current.tag,
pendingProps,
current.key,
current.mode,
);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// 上面的操作相当于半深拷贝了一个root对应的Fiber为workInProgress
// 然后通过Fiber的alternate属性将current和workInProgress两个Fiber相互引用
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
// Needed because Blocks store data on type.
workInProgress.type = current.type;

// We already have an alternate.
// Reset the effect tag.
workInProgress.effectTag = NoEffect;

// The effect list is no longer valid.
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;

if (enableProfilerTimer) {
// We intentionally reset, rather than copy, actualDuration & actualStartTime.
// This prevents time from endlessly accumulating in new commits.
// This has the downside of resetting values for different priority renders,
// But works for yielding (the common case) and should support resuming.
workInProgress.actualDuration = 0;
workInProgress.actualStartTime = -1;
}
}

workInProgress.childExpirationTime = current.childExpirationTime;
workInProgress.expirationTime = current.expirationTime;

workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;

// Clone the dependencies object. This is mutated during the render phase, so
// it cannot be shared with the current fiber.
const currentDependencies = current.dependencies_old;
workInProgress.dependencies_old =
currentDependencies === null
? null
: {
expirationTime: currentDependencies.expirationTime,
firstContext: currentDependencies.firstContext,
responders: currentDependencies.responders,
};

// These will be overridden during the parent's reconciliation
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;

if (enableProfilerTimer) {
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}

return workInProgress;
}
  1. schedulePendingInteractions
1
2
3
4
5
6
7
8
9
function schedulePendingInteractions(root, expirationTime) {
// This is called when work is scheduled on a root.
// It associates the current interactions with the newly-scheduled expiration.
// They will be restored when that expiration is later committed.
if (!enableSchedulerTracing) {
return;
}
scheduleInteractions(root, expirationTime, __interactionsRef.current);
}
  1. scheduleInteractions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
function scheduleInteractions(root, expirationTime, interactions) {
if (!enableSchedulerTracing) {
return;
}
if (interactions.size > 0) { // 首次渲染不会进入该分支
const pendingInteractionMap = root.pendingInteractionMap_old;
const pendingInteractions = pendingInteractionMap.get(expirationTime);
if (pendingInteractions != null) {
interactions.forEach(interaction => {
if (!pendingInteractions.has(interaction)) {
// Update the pending async work count for previously unscheduled interaction.
interaction.__count++;
}

pendingInteractions.add(interaction);
});
} else {
pendingInteractionMap.set(expirationTime, new Set(interactions));

// Update the pending async work count for the current interactions.
interactions.forEach(interaction => {
interaction.__count++;
});
}

const subscriber = __subscriberRef.current;
if (subscriber !== null) {
const threadID = computeThreadID(root, expirationTime);
subscriber.onWorkScheduled(interactions, threadID);
}
}
}
  1. pushInteractions
1
2
3
4
5
6
7
8
function pushInteractions(root) {
if (enableSchedulerTracing) {
const prevInteractions: Set<Interaction> | null = __interactionsRef.current;
__interactionsRef.current = root.memoizedInteractions;
return prevInteractions;
}
return null;
}
  1. workLoopSync
1
2
3
4
5
6
7
8
// The work loop is an extremely hot path. Tell Closure not to inline it.

function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
  1. performUnitOfWork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// renderExpirationTime的值在执行prepareFreshStack函数时被赋予了新值

export const NoMode = 0b00000;
export const StrictMode = 0b00001;
export const BlockingMode = 0b00010;
export const ConcurrentMode = 0b00100;
export const ProfileMode = 0b01000;
export const DebugTracingMode = 0b10000;

function performUnitOfWork(unitOfWork: Fiber): void {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
const current = unitOfWork.alternate;

let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, renderExpirationTime);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else { // 首次渲染进入该分支
next = beginWork(current, unitOfWork, renderExpirationTime);
}

unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}

ReactCurrentOwner.current = null;
}
  1. beginWork

这个函数也太长了吧。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const FundamentalComponent = 20;
export const ScopeComponent = 21;
export const Block = 22;
export const OffscreenComponent = 23;
export const LegacyHiddenComponent = 24;

function beginWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
// 首次expirationTime为0
const updateExpirationTime = workInProgress.expirationTime;

if (current !== null) {
const oldProps = current.memoizedProps;
const newProps = workInProgress.pendingProps;

if (
oldProps !== newProps ||
hasLegacyContextChanged() ||
// Force a re-render if the implementation changed due to hot reload:
(__DEV__ ? workInProgress.type !== current.type : false)
) {
// If props or context changed, mark the fiber as having performed work.
// This may be unset if the props are determined to be equal later (memo).
didReceiveUpdate = true;
} else if (updateExpirationTime < renderExpirationTime) { // updateExpirationTime = renderExpirationTime = 1073741823
didReceiveUpdate = false;
// This fiber does not have any pending work. Bailout without entering
// the begin phase. There's still some bookkeeping we that needs to be done
// in this optimized path, mostly pushing stuff onto the stack.
switch (workInProgress.tag) {
case HostRoot:
pushHostRootContext(workInProgress);
resetHydrationState();
break;
case HostComponent:
pushHostContext(workInProgress);
if (
workInProgress.mode & ConcurrentMode &&
renderExpirationTime !== Never &&
shouldDeprioritizeSubtree(workInProgress.type, newProps)
) {
if (enableSchedulerTracing) {
markSpawnedWork(Never);
}
// Schedule this fiber to re-render at offscreen priority. Then bailout.
workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
return null;
}
break;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
pushLegacyContextProvider(workInProgress);
}
break;
}
case HostPortal:
pushHostContainer(
workInProgress,
workInProgress.stateNode.containerInfo,
);
break;
case ContextProvider: {
const newValue = workInProgress.memoizedProps.value;
pushProvider(workInProgress, newValue);
break;
}
case Profiler:
if (enableProfilerTimer) {
// Profiler should only call onRender when one of its descendants actually rendered.
const hasChildWork =
workInProgress.childExpirationTime >= renderExpirationTime;
if (hasChildWork) {
workInProgress.effectTag |= Update;
}

// Reset effect durations for the next eventual effect phase.
// These are reset during render to allow the DevTools commit hook a chance to read them,
const stateNode = workInProgress.stateNode;
stateNode.effectDuration = 0;
stateNode.passiveEffectDuration = 0;
}
break;
case SuspenseComponent: {
const state: SuspenseState | null = workInProgress.memoizedState;
if (state !== null) {
if (enableSuspenseServerRenderer) {
if (state.dehydrated !== null) {
pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
// We know that this component will suspend again because if it has
// been unsuspended it has committed as a resolved Suspense component.
// If it needs to be retried, it should have work scheduled on it.
workInProgress.effectTag |= DidCapture;

return null;
}
}

// If this boundary is currently timed out, we need to decide
// whether to retry the primary children, or to skip over it and
// go straight to the fallback. Check the priority of the primary
// child fragment.
const primaryChildFragment: Fiber = (workInProgress.child: any);
const primaryChildExpirationTime =
primaryChildFragment.childExpirationTime;
if (primaryChildExpirationTime >= renderExpirationTime) {
// The primary children have pending work. Use the normal path
// to attempt to render the primary children again.
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
} else {
// The primary child fragment does not have pending work marked
// on it...

// ...usually. There's an unfortunate edge case where the fragment
// fiber is not part of the return path of the children, so when
// an update happens, the fragment doesn't get marked during
// setState. This is something we should consider addressing when
// we refactor the Fiber data structure. (There's a test with more
// details; to find it, comment out the following block and see
// which one fails.)
//
// As a workaround, we need to recompute the `childExpirationTime`
// by bubbling it up from the next level of children. This is
// based on similar logic in `resetChildExpirationTime`.
let primaryChild = primaryChildFragment.child;
while (primaryChild !== null) {
const childUpdateExpirationTime = primaryChild.expirationTime;
const childChildExpirationTime =
primaryChild.childExpirationTime;
if (
childUpdateExpirationTime >= renderExpirationTime ||
childChildExpirationTime >= renderExpirationTime
) {
// Found a child with an update with sufficient priority.
// Use the normal path to render the primary children again.
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
}
primaryChild = primaryChild.sibling;
}

pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
// The primary children do not have pending work with sufficient
// priority. Bailout.
const child = bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
if (child !== null) {
// The fallback children have pending work. Skip over the
// primary children and work on the fallback.
return child.sibling;
} else {
return null;
}
}
} else {
pushSuspenseContext(
workInProgress,
setDefaultShallowSuspenseContext(suspenseStackCursor.current),
);
}
break;
}
case SuspenseListComponent: {
const didSuspendBefore =
(current.effectTag & DidCapture) !== NoEffect;

const hasChildWork =
workInProgress.childExpirationTime >= renderExpirationTime;

if (didSuspendBefore) {
if (hasChildWork) {
// If something was in fallback state last time, and we have all the
// same children then we're still in progressive loading state.
// Something might get unblocked by state updates or retries in the
// tree which will affect the tail. So we need to use the normal
// path to compute the correct tail.
return updateSuspenseListComponent(
current,
workInProgress,
renderExpirationTime,
);
}
// If none of the children had any work, that means that none of
// them got retried so they'll still be blocked in the same way
// as before. We can fast bail out.
workInProgress.effectTag |= DidCapture;
}

// If nothing suspended before and we're rendering the same children,
// then the tail doesn't matter. Anything new that suspends will work
// in the "together" mode, so we can continue from the state we had.
const renderState = workInProgress.memoizedState;
if (renderState !== null) {
// Reset to the "together" mode in case we've started a different
// update in the past but didn't complete it.
renderState.rendering = null;
renderState.tail = null;
renderState.lastEffect = null;
}
pushSuspenseContext(workInProgress, suspenseStackCursor.current);

if (hasChildWork) {
break;
} else {
// If none of the children had any work, that means that none of
// them got retried so they'll still be blocked in the same way
// as before. We can fast bail out.
return null;
}
}
}
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
} else {
// An update was scheduled on this fiber, but there are no new props
// nor legacy context. Set this to false. If an update queue or context
// consumer produces a changed value, it will set this to true. Otherwise,
// the component will assume the children have not changed and bail out.
didReceiveUpdate = false;
}
} else {
didReceiveUpdate = false; // 首次渲染直接到这里
}

// Before entering the begin phase, clear pending update priority.
// TODO: This assumes that we're about to evaluate the component and process
// the update queue. However, there's an exception: SimpleMemoComponent
// sometimes bails out later in the begin phase. This indicates that we should
// move this assignment out of the common path and into each branch.
workInProgress.expirationTime = NoWork;

switch (workInProgress.tag) {
case IndeterminateComponent: {
return mountIndeterminateComponent(
current,
workInProgress,
workInProgress.type,
renderExpirationTime,
);
}
case LazyComponent: {
const elementType = workInProgress.elementType;
return mountLazyComponent(
current,
workInProgress,
elementType,
updateExpirationTime,
renderExpirationTime,
);
}
case FunctionComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case ClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return updateClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case HostRoot: // 首次渲染第一次进入这个分支
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent:
return updateHostComponent(current, workInProgress, renderExpirationTime);
case HostText:
return updateHostText(current, workInProgress);
case SuspenseComponent:
return updateSuspenseComponent(
current,
workInProgress,
renderExpirationTime,
);
case HostPortal:
return updatePortalComponent(
current,
workInProgress,
renderExpirationTime,
);
case ForwardRef: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === type
? unresolvedProps
: resolveDefaultProps(type, unresolvedProps);
return updateForwardRef(
current,
workInProgress,
type,
resolvedProps,
renderExpirationTime,
);
}
case Fragment:
return updateFragment(current, workInProgress, renderExpirationTime);
case Mode:
return updateMode(current, workInProgress, renderExpirationTime);
case Profiler:
return updateProfiler(current, workInProgress, renderExpirationTime);
case ContextProvider:
return updateContextProvider(
current,
workInProgress,
renderExpirationTime,
);
case ContextConsumer:
return updateContextConsumer(
current,
workInProgress,
renderExpirationTime,
);
case MemoComponent: {
const type = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
// Resolve outer props first, then resolve inner props.
let resolvedProps = resolveDefaultProps(type, unresolvedProps);
resolvedProps = resolveDefaultProps(type.type, resolvedProps);
return updateMemoComponent(
current,
workInProgress,
type,
resolvedProps,
updateExpirationTime,
renderExpirationTime,
);
}
case SimpleMemoComponent: {
return updateSimpleMemoComponent(
current,
workInProgress,
workInProgress.type,
workInProgress.pendingProps,
updateExpirationTime,
renderExpirationTime,
);
}
case IncompleteClassComponent: {
const Component = workInProgress.type;
const unresolvedProps = workInProgress.pendingProps;
const resolvedProps =
workInProgress.elementType === Component
? unresolvedProps
: resolveDefaultProps(Component, unresolvedProps);
return mountIncompleteClassComponent(
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
case SuspenseListComponent: {
return updateSuspenseListComponent(
current,
workInProgress,
renderExpirationTime,
);
}
case FundamentalComponent: {
if (enableFundamentalAPI) {
return updateFundamentalComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
case ScopeComponent: {
if (enableScopeAPI) {
return updateScopeComponent(
current,
workInProgress,
renderExpirationTime,
);
}
break;
}
case Block: {
if (enableBlocksAPI) {
const block = workInProgress.type;
const props = workInProgress.pendingProps;
return updateBlock(
current,
workInProgress,
block,
props,
renderExpirationTime,
);
}
break;
}
}
invariant(
false,
'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
'React. Please file an issue.',
workInProgress.tag,
);
}

=================================================================

注意:由于判断失误。。。首次渲染不会调用19~26号函数。。。

  1. pushHostRootContext

首次渲染时,处理root对应的fiber时会调用这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function pushHostRootContext(workInProgress) {
const root = (workInProgress.stateNode: FiberRoot);
if (root.pendingContext) { // root.pengingContext = null
pushTopLevelContextObject(
workInProgress,
root.pendingContext,
root.pendingContext !== root.context,
);
} else if (root.context) { // root.context = {}
// Should always be set
pushTopLevelContextObject(workInProgress, root.context, false);
}
pushHostContainer(workInProgress, root.containerInfo);
}
  1. pushTopLevelContextObject

首次渲染的调用: pushTopLevelContextObject(workInProgress, root.context, false);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function pushTopLevelContextObject(
fiber: Fiber,
context: Object,
didChange: boolean,
): void {
if (disableLegacyContext) { // false
return;
} else {
invariant(
contextStackCursor.current === emptyContextObject,
'Unexpected context found on stack. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
// contextStackCursor = { current: {} }
// context = {}
// 首次渲染执行完这个函数的结果是
// index = 0
// valueStack = [{}]
// contextStackCursor = { current: {} } current的值变为新的空对象
push(contextStackCursor, context, fiber);
// didPerformWorkStackCursor = { current: false }
// didChange = false
// 就结果而言没有变化
push(didPerformWorkStackCursor, didChange, fiber);
// 上面两个函数执行完的结果:
// index = 1
// valueStack = [{}, false]
}
}
  1. push
1
2
3
4
5
6
7
8
9
// index = -1
// valueStack = []
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
index++;

valueStack[index] = cursor.current;

cursor.current = value;
}
  1. pushHostContainer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 执行下面指令之前的相关变量值
// index = 1
// valueStack = [{}, false]
// 分别是:contextStackCursor和didPerformWorkStackCursor

// const NO_CONTEXT: NoContextT = ({}: any);
// const rootInstanceStackCursor: StackCursor<
// Container | NoContextT,
// > = createCursor(NO_CONTEXT);

// const contextStackCursor: StackCursor<HostContext | NoContextT> = createCursor(
// NO_CONTEXT,
// );
// const contextFiberStackCursor: StackCursor<Fiber | NoContextT> = createCursor(
// NO_CONTEXT,
// );

function pushHostContainer(fiber: Fiber, nextRootInstance: Container) {
// Push current root instance onto the stack;
// This allows us to reset root when portals are popped.
// index = 2
// valueStack = [{}, false, {}]
// rootInstanceStackCursor = { current: container }
push(rootInstanceStackCursor, nextRootInstance, fiber);
// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
// index = 3
// valueStack = [{}, false, {}, {}]
// contextFiberStackCursor = { current: workInProgress }
push(contextFiberStackCursor, fiber, fiber);

// Finally, we need to push the host context to the stack.
// However, we can't just call getRootHostContext() and push it because
// we'd have a different number of entries on the stack depending on
// whether getRootHostContext() throws somewhere in renderer code or not.
// So we push an empty value first. This lets us safely unwind on errors.

// index = 4
// valueStack = [{}, false, {}, {}, {}]
// contextStackCursor = { current: {} }
push(contextStackCursor, NO_CONTEXT, fiber);
const nextRootContext = getRootHostContext(nextRootInstance); // 计算得到:http://www.w3.org/1999/xhtml
// Now that we know this function doesn't throw, replace it.
// index = 3
// valueStack = [{}, false, {}, {}, null]
// contextStackCursor = { current: {} }
pop(contextStackCursor, fiber);
// index = 4
// valueStack = [{}, false, {}, {}, {} ]
// contextStackCursor = { current: "http://www.w3.org/1999/xhtml" }
push(contextStackCursor, nextRootContext, fiber);
}
  1. getRootHostContext

这是进入react内部第一次遇到直接操纵dom的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function getRootHostContext(
rootContainerInstance: Container,
): HostContext {
let type;
let namespace;
const nodeType = rootContainerInstance.nodeType; // 1 ELEMENT_NODE (首次渲染)
switch (nodeType) {
case DOCUMENT_NODE:
case DOCUMENT_FRAGMENT_NODE: {
type = nodeType === DOCUMENT_NODE ? '#document' : '#fragment';
const root = (rootContainerInstance: any).documentElement;
namespace = root ? root.namespaceURI : getChildNamespace(null, '');
break;
}
default: { // 首次渲染走这个分支
const container: any =
nodeType === COMMENT_NODE
? rootContainerInstance.parentNode
: rootContainerInstance;
const ownNamespace = container.namespaceURI || null; // http://www.w3.org/1999/xhtml
type = container.tagName; // 'DIV'
namespace = getChildNamespace(ownNamespace, type); // http://www.w3.org/1999/xhtml
break;
}
}

return namespace;
}
  1. getChildNamespace

完全没看懂这个函数。。。为啥要绕一圈。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';

function getChildNamespace(
parentNamespace: string | null,
type: string,
): string {
if (parentNamespace == null || parentNamespace === HTML_NAMESPACE) {
// No (or default) parent namespace: potential entry point.
return getIntrinsicNamespace(type);
}
if (parentNamespace === SVG_NAMESPACE && type === 'foreignObject') {
// We're leaving SVG.
return HTML_NAMESPACE;
}
// By default, pass namespace below.
return parentNamespace;
}
  1. pop
1
2
3
4
5
6
7
8
9
10
11
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
if (index < 0) {
return;
}

cursor.current = valueStack[index];

valueStack[index] = null;

index--;
}
  1. bailoutOnAlreadyFinishedWork

此处调用: return bailoutOnAlreadyFinishedWork( current, workInProgress, renderExpirationTime, );

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function bailoutOnAlreadyFinishedWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
if (current !== null) {
// Reuse previous dependencies
workInProgress.dependencies_old = current.dependencies_old;
}

if (enableProfilerTimer) {
// Don't update "base" render times for bailouts.
stopProfilerTimerIfRunning(workInProgress);
}

const updateExpirationTime = workInProgress.expirationTime;
if (updateExpirationTime !== NoWork) {
markUnprocessedUpdateTime(updateExpirationTime);
}

// Check if the children have any pending work.
const childExpirationTime = workInProgress.childExpirationTime;
if (childExpirationTime < renderExpirationTime) {
// The children don't have any work either. We can skip them.
// TODO: Once we add back resuming, we should check if the children are
// a work-in-progress set. If so, we need to transfer their effects.
return null;
} else {
// This fiber doesn't have work, but its subtree does. Clone the child
// fibers and continue.
cloneChildFibers(current, workInProgress);
return workInProgress.child;
}
}

==================================================================================

  1. updateHostRoot

此刻的各个属性值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
updateQueue: {
effects: null,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
},
element: {
$$typeof: Symbol(react.element),
key: null,
ref: null,
type: Symbol(react.strict_mode)
_owner: null,
props: {
children: {
$$typeof: Symbol(react.element),
key: null,
props: {},
ref: null,
type: App(),
_owner: null,
}
},
}
}

pendingProps: null

memoizedState: {
element: {
$$typeof: Symbol(react.element),
key: null,
ref: null,
type: Symbol(react.strict_mode)
_owner: null,
props: {
children: {
$$typeof: Symbol(react.element),
key: null,
props: {},
ref: null,
type: App(),
_owner: null,
}
},
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
function updateHostRoot(current, workInProgress, renderExpirationTime) {
pushHostRootContext(workInProgress); // 见上
const updateQueue = workInProgress.updateQueue;
const nextProps = workInProgress.pendingProps;
const prevState = workInProgress.memoizedState;
const prevChildren = prevState !== null ? prevState.element : null; // null
cloneUpdateQueue(current, workInProgress);
processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
const nextState = workInProgress.memoizedState;
// Caution: React DevTools currently depends on this property
// being called "element".
const nextChildren = nextState.element;
if (nextChildren === prevChildren) {
// If the state is the same as before, that's a bailout because we had
// no work that expires at this time.
resetHydrationState();
return bailoutOnAlreadyFinishedWork(
current,
workInProgress,
renderExpirationTime,
);
}
const root: FiberRoot = workInProgress.stateNode;
if (root.hydrate && enterHydrationState(workInProgress)) {
// If we don't have any current children this might be the first pass.
// We always try to hydrate. If this isn't a hydration pass there won't
// be any children to hydrate which is effectively the same thing as
// not hydrating.

if (supportsHydration) {
const mutableSourceEagerHydrationData =
root.mutableSourceEagerHydrationData;
if (mutableSourceEagerHydrationData != null) {
for (let i = 0; i < mutableSourceEagerHydrationData.length; i += 2) {
const mutableSource = ((mutableSourceEagerHydrationData[
i
]: any): MutableSource<any>);
const version = mutableSourceEagerHydrationData[i + 1];
setWorkInProgressVersion(mutableSource, version);
}
}
}

const child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
workInProgress.child = child;

let node = child;
while (node) {
// Mark each child as hydrating. This is a fast path to know whether this
// tree is part of a hydrating tree. This is used to determine if a child
// node has fully mounted yet, and for scheduling event replaying.
// Conceptually this is similar to Placement in that a new subtree is
// inserted into the React tree here. It just happens to not need DOM
// mutations because it already exists.
node.effectTag = (node.effectTag & ~Placement) | Hydrating;
node = node.sibling;
}
} else {
// Otherwise reset hydration state in case we aborted and resumed another
// root.
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
resetHydrationState();
}
return workInProgress.child;
}
  1. cloneUpdateQueue

这个函数是将current的updateQueue浅拷贝给workInProgress的updateQueue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function cloneUpdateQueue<State>(
current: Fiber,
workInProgress: Fiber,
): void {
// Clone the update queue from current. Unless it's already a clone.
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
if (queue === currentQueue) {
const clone: UpdateQueue<State> = {
baseState: currentQueue.baseState,
firstBaseUpdate: currentQueue.firstBaseUpdate,
lastBaseUpdate: currentQueue.lastBaseUpdate,
shared: currentQueue.shared,
effects: currentQueue.effects,
};
workInProgress.updateQueue = clone;
}
}
  1. processUpdateQueue

此处调用:processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Global state that is reset at the beginning of calling `processUpdateQueue`.
// It should only be read right after calling `processUpdateQueue`, via
// `checkHasForceUpdateAfterProcessing`.
let hasForceUpdate = false;

queue.shared.pending = {
callback: null,
expirationTime: 1073741823,
next: pending, // next引用另一个pending,此处是它所在的pending
suspenseConfig: null,
tag: 0,
payload: {
element: {
$$typeof: Symbol(react.element),
key: null,
ref: null,
type: Symbol(react.strict_mode),
_owner: null,
props: {
children: {
$$typeof: Symbol(react.element),
key: null,
props: {},
ref: null,
type: App(),
_owner: null,
},
}
}
}
}

queue: {
baseState: null,
effects: null,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: 见上
}

// The work left over by components that were visited during this render. Only
// includes unprocessed updates, not work in bailed out children.
let workInProgressRootNextUnprocessedUpdateTime: ExpirationTime = NoWork;

export function markUnprocessedUpdateTime(
expirationTime: ExpirationTime,
): void {
if (expirationTime > workInProgressRootNextUnprocessedUpdateTime) {
workInProgressRootNextUnprocessedUpdateTime = expirationTime;
}
}

export function processUpdateQueue<State>(
workInProgress: Fiber,
props: any,
instance: any,
renderExpirationTime: ExpirationTime,
): void {
// This is always non-null on a ClassComponent or HostRoot
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);

hasForceUpdate = false;

let firstBaseUpdate = queue.firstBaseUpdate; // null
let lastBaseUpdate = queue.lastBaseUpdate; // null

// Check if there are pending updates. If so, transfer them to the base queue.
let pendingQueue = queue.shared.pending;
if (pendingQueue !== null) {
queue.shared.pending = null;

// The pending queue is circular. Disconnect the pointer between first
// and last so that it's non-circular.
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = lastPendingUpdate.next;
// lastPendingUpdate === firstPendingUpdate true
lastPendingUpdate.next = null;
// Append pending updates to base queue
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
lastBaseUpdate.next = firstPendingUpdate;
}
lastBaseUpdate = lastPendingUpdate;

// If there's a current queue, and it's different from the base queue, then
// we need to transfer the updates to that queue, too. Because the base
// queue is a singly-linked list with no cycles, we can append to both
// lists and take advantage of structural sharing.
// TODO: Pass `current` as argument
const current = workInProgress.alternate;
if (current !== null) {
// This is always non-null on a ClassComponent or HostRoot
const currentQueue: UpdateQueue<State> = (current.updateQueue: any);
// currentQueue = {
// baseState: null,
// effects: null,
// firstBaseUpdate: null,
// lastBaseUpdate: null,
// shared: {
// pending: null,
// },
// }
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
if (currentLastBaseUpdate !== lastBaseUpdate) {
if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else {
currentLastBaseUpdate.next = firstPendingUpdate;
}
currentQueue.lastBaseUpdate = lastPendingUpdate;
}
}
}

// These values may change as we process the queue.
if (firstBaseUpdate !== null) {
// Iterate through the list of updates to compute the result.
let newState = queue.baseState;
let newExpirationTime = NoWork;

let newBaseState = null;
let newFirstBaseUpdate = null;
let newLastBaseUpdate = null;

let update = firstBaseUpdate;
do {
const updateExpirationTime = update.expirationTime;
if (updateExpirationTime < renderExpirationTime) {
// Priority is insufficient. Skip this update. If this is the first
// skipped update, the previous update/state is the new base
// update/state.
const clone: Update<State> = {
expirationTime: update.expirationTime,
suspenseConfig: update.suspenseConfig,

tag: update.tag,
payload: update.payload,
callback: update.callback,

next: null,
};
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
newBaseState = newState;
} else {
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
// Update the remaining priority in the queue.
if (updateExpirationTime > newExpirationTime) {
newExpirationTime = updateExpirationTime;
}
} else {
// This update does have sufficient priority.

if (newLastBaseUpdate !== null) {
const clone: Update<State> = {
expirationTime: Sync, // This update is going to be committed so we never want uncommit it.
suspenseConfig: update.suspenseConfig,

tag: update.tag,
payload: update.payload,
callback: update.callback,

next: null,
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}

// Mark the event time of this update as relevant to this render pass.
// TODO: This should ideally use the true event time of this update rather than
// its priority which is a derived and not reverseable value.
// TODO: We should skip this update if it was already committed but currently
// we have no way of detecting the difference between a committed and suspended
// update here.
// 首次渲染第一次什么都没干
markRenderEventTimeAndConfig(
updateExpirationTime,
update.suspenseConfig,
);

// Process this update.
newState = getStateFromUpdate(
workInProgress,
queue,
update,
newState,
props,
instance,
);
const callback = update.callback;
if (callback !== null) {
workInProgress.effectTag |= Callback;
const effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
}
update = update.next;
if (update === null) {
pendingQueue = queue.shared.pending;
if (pendingQueue === null) {
break;
} else {
// An update was scheduled from inside a reducer. Add the new
// pending updates to the end of the list and keep processing.
const lastPendingUpdate = pendingQueue;
// Intentionally unsound. Pending updates form a circular list, but we
// unravel them when transferring them to the base queue.
const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>);
lastPendingUpdate.next = null;
update = firstPendingUpdate;
queue.lastBaseUpdate = lastPendingUpdate;
queue.shared.pending = null;
}
}
} while (true);

if (newLastBaseUpdate === null) {
newBaseState = newState;
}

queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;

// Set the remaining expiration time to be whatever is remaining in the queue.
// This should be fine because the only two other things that contribute to
// expiration time are props and context. We're already in the middle of the
// begin phase by the time we start processing the queue, so we've already
// dealt with the props. Context in components that specify
// shouldComponentUpdate is tricky; but we'll have to account for
// that regardless.
markUnprocessedUpdateTime(newExpirationTime);
workInProgress.expirationTime = newExpirationTime;
workInProgress.memoizedState = newState;
}
}
  1. markRenderEventTimeAndConfig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Most recent event time among processed updates during this render.
// This is conceptually a time stamp but expressed in terms of an ExpirationTime
// because we deal mostly with expiration times in the hot path, so this avoids
// the conversion happening in the hot path.
let workInProgressRootLatestProcessedExpirationTime: ExpirationTime = Sync;
// Idle is slightly higher priority than Never. It must completely finish in
// order to be consistent.
export const Idle = 2;

function markRenderEventTimeAndConfig(
expirationTime: ExpirationTime, // 1073741823
suspenseConfig: null | SuspenseConfig, // null
): void {
if (
expirationTime < workInProgressRootLatestProcessedExpirationTime &&
expirationTime > Idle
) {
workInProgressRootLatestProcessedExpirationTime = expirationTime;
}
if (suspenseConfig !== null) {
if (
expirationTime < workInProgressRootLatestSuspenseTimeout &&
expirationTime > Idle
) {
workInProgressRootLatestSuspenseTimeout = expirationTime;
// Most of the time we only have one config and getting wrong is not bad.
workInProgressRootCanSuspendUsingConfig = suspenseConfig;
}
}
}
  1. getStateFromUpdate

此处调用:getStateFromUpdate(workInProgress, workInProgress.updateQueue, firstPendingUpdate, null, null, null)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
export const UpdateState = 0;
export const ReplaceState = 1;
export const ForceUpdate = 2;
export const CaptureUpdate = 3;

function getStateFromUpdate<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
update: Update<State>,
prevState: State,
nextProps: any,
instance: any,
): any {
switch (update.tag) { // 0
case ReplaceState: {
const payload = update.payload;
if (typeof payload === 'function') {
// Updater function
const nextState = payload.call(instance, prevState, nextProps);
return nextState;
}
// State object
return payload;
}
case CaptureUpdate: {
workInProgress.effectTag =
(workInProgress.effectTag & ~ShouldCapture) | DidCapture;
}
// Intentional fallthrough
case UpdateState: {
// 首次渲染第一次
// payload = {
// $$typeof: Symbol(react.element)
// key: null,
// props: {
// children: {
// $$typeof: Symbol(react.element),
// key: null,
// props: {},
// ref: null,
// type: f App(),
// _owner: null
// }
// }
// ref: null,
// type: Symbol(react.strict_mode)
// _owner: null
// }
const payload = update.payload;
let partialState;
if (typeof payload === 'function') {
// Updater function
partialState = payload.call(instance, prevState, nextProps);
} else {
// Partial state object
partialState = payload;
}
if (partialState === null || partialState === undefined) {
// Null and undefined are treated as no-ops.
return prevState;
}
// Merge the partial state and the previous state.
return Object.assign({}, prevState, partialState);
}
case ForceUpdate: {
hasForceUpdate = true;
return prevState;
}
}
return prevState;
}
  1. reconcileChildren

此处调用: reconcileChildren( current, workInProgress, nextChildren, renderExpirationTime, );

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.

// If we had any progressed work already, that is invalid at this point so
// let's throw it out.

// ================================进入下面这个函数时的各个参数值==================
// child: null
// nextChildren: {
// $$typeof: Symbol(react.element)
// key: null
// props: {
// children: {
// $$typeof: Symbol(react.element)
// key: null
// props: {}
// ref: null
// type: ƒ App()
// _owner: null
// }
// }
// ref: null
// type: Symbol(react.strict_mode)
// _owner: null
// }
// renderExpirationTime: 1073741823
// ===========================================================================
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
);
}
}
  1. reconcileChildFibers
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
// nextChildren: {
// $$typeof: Symbol(react.element),
// key: null,
// ref: null,
// type: Symbol(react.strict_mode)
// _owner: null,
// props: {
// children: {
// $$typeof: Symbol(react.element),
// key: null,
// props: {},
// ref: null,
// type: App(),
// _owner: null,
// }
// },
// }

// REACT_ELEMENT_TYPE = symbolFor('react.element');
// shouldTrackSideEffects = true;
// export const Placement = 0b00000000000010;

function placeSingleChild(newFiber: Fiber): Fiber {
// This is simpler for the single child case. We only need to do a
// placement for inserting new children.
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.effectTag = Placement;
}
return newFiber;
}

function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
switch (child.tag) {
case Fragment: {
if (element.type === REACT_FRAGMENT_TYPE) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props.children);
existing.return = returnFiber;
return existing;
}
break;
}
case Block:
if (enableBlocksAPI) {
let type = element.type;
if (type.$$typeof === REACT_LAZY_TYPE) {
type = resolveLazyType(type);
}
if (type.$$typeof === REACT_BLOCK_TYPE) {
// The new Block might not be initialized yet. We need to initialize
// it in case initializing it turns out it would match.
if (
((type: any): BlockComponent<any, any>)._render ===
(child.type: BlockComponent<any, any>)._render
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.type = type;
existing.return = returnFiber;
return existing;
}
}
}
// We intentionally fallthrough here if enableBlocksAPI is not on.
// eslint-disable-next-lined no-fallthrough
default: {
if (
child.elementType === element.type ||
// Keep this check inline so it only runs on the false path:
(__DEV__
? isCompatibleFamilyForHotReloading(child, element)
: false)
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.ref = coerceRef(returnFiber, child, element);
existing.return = returnFiber;
return existing;
}
break;
}
}
// Didn't match.
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}

if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
expirationTime,
element.key,
);
created.return = returnFiber;
return created;
} else {
// 进入这个分支
const created = createFiberFromElement(
element,
returnFiber.mode,
expirationTime,
);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}
}

// This API will tag the children with the side-effect of the reconciliation
// itself. They will be added to the side-effect list as we pass through the
// children and the parent.
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
// This function is not recursive.
// If the top level item is an array, we treat it as a set of children,
// not as a fragment. Nested arrays on the other hand will be treated as
// fragment nodes. Recursion happens at the normal flow.

// Handle top level unkeyed fragments as if they were arrays.
// This leads to an ambiguity between <>{[...]}</> and <>...</>.
// We treat the ambiguous cases above the same.
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null; // false
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}

// Handle object types
const isObject = typeof newChild === 'object' && newChild !== null; // true

if (isObject) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
}
}

if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
expirationTime,
),
);
}

if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}

if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}

if (isObject) {
throwOnInvalidObjectType(returnFiber, newChild);
}

if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
// If the new child is undefined, and the return fiber is a composite
// component, throw an error. If Fiber return types are disabled,
// we already threw above.
switch (returnFiber.tag) {
case ClassComponent: {
}
// Intentionally fall through to the next case, which handles both
// functions and classes
// eslint-disable-next-lined no-fallthrough
case FunctionComponent: {
const Component = returnFiber.type;
invariant(
false,
'%s(...): Nothing was returned from render. This usually means a ' +
'return statement is missing. Or, to render nothing, ' +
'return null.',
Component.displayName || Component.name || 'Component',
);
}
}
}

// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
  1. createFiberFromElement
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function createFiberFromElement(
element: ReactElement,
mode: TypeOfMode,
expirationTime: ExpirationTime,
): Fiber {
let owner = null;
const type = element.type;
const key = element.key;
const pendingProps = element.props;
const fiber = createFiberFromTypeAndProps(
type,
key,
pendingProps,
owner,
mode,
expirationTime,
);
return fiber;
}
  1. createFiberFromTypeAndProps
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
// type: Symbol(react.strict_mode)
// key: null,
// pendingProps: {
// children: {
// $$typeof: Symbol(react.element)
// key: null
// props: {}
// ref: null
// type: ƒ App()
// _owner: null
// }
// }
// owner: null
// mode: 0
// expirationTime: 1073741823
export const NoMode = 0b00000;
export const StrictMode = 0b00001;
export const IndeterminateComponent = 2; // Before we know whether it is function or class

function createFiberFromTypeAndProps(
type: any, // React$ElementType
key: null | string,
pendingProps: any,
owner: null | Fiber,
mode: TypeOfMode,
expirationTime: ExpirationTime,
): Fiber {
let fiberTag = IndeterminateComponent; // 2
// The resolved type is set if we know what the final type will be. I.e. it's not lazy.
let resolvedType = type; // Symbol(react.strict_mode)
if (typeof type === 'function') {
if (shouldConstruct(type)) {
fiberTag = ClassComponent;
} else {
}
} else if (typeof type === 'string') {
fiberTag = HostComponent;
} else {
getTag: switch (type) {
case REACT_FRAGMENT_TYPE:
return createFiberFromFragment(
pendingProps.children,
mode,
expirationTime,
key,
);
case REACT_DEBUG_TRACING_MODE_TYPE:
fiberTag = Mode;
mode |= DebugTracingMode;
break;
case REACT_STRICT_MODE_TYPE: // 进入这个分支
fiberTag = Mode; // 8
mode |= StrictMode; // 0b00001
break;
case REACT_PROFILER_TYPE:
return createFiberFromProfiler(pendingProps, mode, expirationTime, key);
case REACT_SUSPENSE_TYPE:
return createFiberFromSuspense(pendingProps, mode, expirationTime, key);
case REACT_SUSPENSE_LIST_TYPE:
return createFiberFromSuspenseList(
pendingProps,
mode,
expirationTime,
key,
);
default: {
if (typeof type === 'object' && type !== null) {
switch (type.$$typeof) {
case REACT_PROVIDER_TYPE:
fiberTag = ContextProvider;
break getTag;
case REACT_CONTEXT_TYPE:
// This is a consumer
fiberTag = ContextConsumer;
break getTag;
case REACT_FORWARD_REF_TYPE:
fiberTag = ForwardRef;
break getTag;
case REACT_MEMO_TYPE:
fiberTag = MemoComponent;
break getTag;
case REACT_LAZY_TYPE:
fiberTag = LazyComponent;
resolvedType = null;
break getTag;
case REACT_BLOCK_TYPE:
fiberTag = Block;
break getTag;
case REACT_FUNDAMENTAL_TYPE:
if (enableFundamentalAPI) {
return createFiberFromFundamental(
type,
pendingProps,
mode,
expirationTime,
key,
);
}
break;
case REACT_SCOPE_TYPE:
if (enableScopeAPI) {
return createFiberFromScope(
type,
pendingProps,
mode,
expirationTime,
key,
);
}
}
}
let info = '';
invariant(
false,
'Element type is invalid: expected a string (for built-in ' +
'components) or a class/function (for composite components) ' +
'but got: %s.%s',
type == null ? type : typeof type,
info,
);
}
}
}

const fiber = createFiber(fiberTag, pendingProps, key, mode); // createFiber(8, {
// children: {
// $$typeof: Symbol(react.element)
// key: null
// props: {}
// ref: null
// type: ƒ App()
// _owner: null
// }
// }, null, 0b00001)
fiber.elementType = type;
fiber.type = resolvedType;
fiber.expirationTime = expirationTime;

return fiber;
}
  1. coerceRef
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
function coerceRef(
returnFiber: Fiber, //
current: Fiber | null, // null
element: ReactElement,
) {
const mixedRef = element.ref; // null
if (
mixedRef !== null &&
typeof mixedRef !== 'function' &&
typeof mixedRef !== 'object'
) {
if (element._owner) {
const owner: ?Fiber = (element._owner: any);
let inst;
if (owner) {
const ownerFiber = ((owner: any): Fiber);
invariant(
ownerFiber.tag === ClassComponent,
'Function components cannot have string refs. ' +
'We recommend using useRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://fb.me/react-strict-mode-string-ref',
);
inst = ownerFiber.stateNode;
}
invariant(
inst,
'Missing owner for string ref %s. This error is likely caused by a ' +
'bug in React. Please file an issue.',
mixedRef,
);
const stringRef = '' + mixedRef;
// Check if previous string ref matches new string ref
if (
current !== null &&
current.ref !== null &&
typeof current.ref === 'function' &&
current.ref._stringRef === stringRef
) {
return current.ref;
}
const ref = function(value) {
let refs = inst.refs;
if (refs === emptyRefsObject) {
// This is a lazy pooled frozen object, so we need to initialize.
refs = inst.refs = {};
}
if (value === null) {
delete refs[stringRef];
} else {
refs[stringRef] = value;
}
};
ref._stringRef = stringRef;
return ref;
} else {
invariant(
typeof mixedRef === 'string',
'Expected ref to be a function, a string, an object returned by React.createRef(), or null.',
);
invariant(
element._owner,
'Element ref was specified as a string (%s) but no owner was set. This could happen for one of' +
' the following reasons:\n' +
'1. You may be adding a ref to a function component\n' +
"2. You may be adding a ref to a component that was not created inside a component's render method\n" +
'3. You have multiple copies of React loaded\n' +
'See https://fb.me/react-refs-must-have-owner for more information.',
mixedRef,
);
}
}
return mixedRef;
}
  1. updateMode
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function updateMode(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
) {
const nextChildren = workInProgress.pendingProps.children;
reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
}
  1. mountIndeterminateComponent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
export const disableLegacyContext = false;

function getUnmaskedContext(
workInProgress: Fiber,
Component: Function,
didPushOwnContextIfProvider: boolean,
): Object {
if (disableLegacyContext) {
return emptyContextObject;
} else {
if (didPushOwnContextIfProvider && isContextProvider(Component)) {
// If the fiber is a context provider itself, when we read its context
// we may have already pushed its own child context on the stack. A context
// provider should not "see" its own child context. Therefore we read the
// previous (parent) context instead for a context provider.
return previousContext;
}
return contextStackCursor.current;
}
}

function getMaskedContext(
workInProgress: Fiber,
unmaskedContext: Object,
): Object {
if (disableLegacyContext) {
return emptyContextObject;
} else {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyContextObject;
}

// Avoid recreating masked context unless unmasked context has changed.
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
// This may trigger infinite loops if componentWillReceiveProps calls setState.
const instance = workInProgress.stateNode;
if (
instance &&
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
) {
return instance.__reactInternalMemoizedMaskedChildContext;
}

const context = {};
for (const key in contextTypes) {
context[key] = unmaskedContext[key];
}

if (__DEV__) {
const name = getComponentName(type) || 'Unknown';
checkPropTypes(contextTypes, context, 'context', name);
}

// Cache unmasked context so we can avoid recreating masked context unless necessary.
// Context is created before the class component is instantiated so check for instance.
if (instance) {
cacheContext(workInProgress, unmaskedContext, context);
}

return context;
}
}

// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /* */ 0b00000000000000;
export const PerformedWork = /* */ 0b00000000000001;

function mountIndeterminateComponent(
_current,
workInProgress,
Component,
renderExpirationTime,
) {
if (_current !== null) {
// An indeterminate component only mounts if it suspended inside a non-
// concurrent tree, in an inconsistent state. We want to treat it like
// a new mount, even though an empty version of it already committed.
// Disconnect the alternate pointers.
_current.alternate = null;
workInProgress.alternate = null;
// Since this is conceptually a new fiber, schedule a Placement effect
workInProgress.effectTag |= Placement;
}

const props = workInProgress.pendingProps;
let context;
if (!disableLegacyContext) {
const unmaskedContext = getUnmaskedContext(
workInProgress,
Component,
false,
);
context = getMaskedContext(workInProgress, unmaskedContext); // {}
}

prepareToReadContext(workInProgress, renderExpirationTime);
let value;

value = renderWithHooks(
null,
workInProgress,
Component,
props,
context,
renderExpirationTime,
);
// React DevTools reads this flag.
workInProgress.effectTag |= PerformedWork;

if (
// Run these checks in production only if the flag is off.
// Eventually we'll delete this branch altogether.
!disableModulePatternComponents &&
typeof value === 'object' &&
value !== null &&
typeof value.render === 'function' &&
value.$$typeof === undefined
) {
// Proceed under the assumption that this is a class instance
workInProgress.tag = ClassComponent;

// Throw out any hooks that were used.
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;

// Push context providers early to prevent context stack mismatches.
// During mounting we don't know the child context yet as the instance doesn't exist.
// We will invalidate the child context in finishClassComponent() right after rendering.
let hasContext = false;
if (isLegacyContextProvider(Component)) {
hasContext = true;
pushLegacyContextProvider(workInProgress);
} else {
hasContext = false;
}

workInProgress.memoizedState =
value.state !== null && value.state !== undefined ? value.state : null;

initializeUpdateQueue(workInProgress);

const getDerivedStateFromProps = Component.getDerivedStateFromProps;
if (typeof getDerivedStateFromProps === 'function') {
applyDerivedStateFromProps(
workInProgress,
Component,
getDerivedStateFromProps,
props,
);
}

adoptClassInstance(workInProgress, value);
mountClassInstance(workInProgress, Component, props, renderExpirationTime);
return finishClassComponent(
null,
workInProgress,
Component,
true,
hasContext,
renderExpirationTime,
);
} else {
// Proceed under the assumption that this is a function component
workInProgress.tag = FunctionComponent;
reconcileChildren(null, workInProgress, value, renderExpirationTime);
return workInProgress.child;
}
}
  1. renderWithHooks
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// These are set right before calling the component.
let renderExpirationTime: ExpirationTime = NoWork;
// The work-in-progress fiber. I've named it differently to distinguish it from
// the work-in-progress hook.
let currentlyRenderingFiber: Fiber = (null: any);

// Where an update was scheduled only during the current render pass. This
// gets reset after each attempt.
// TODO: Maybe there's some way to consolidate this with
// `didScheduleRenderPhaseUpdate`. Or with `numberOfReRenders`.
let didScheduleRenderPhaseUpdateDuringThisPass: boolean = false;

// Hooks are stored as a linked list on the fiber's memoizedState field. The
// current hook list is the list that belongs to the current fiber. The
// work-in-progress hook list is a new list that will be added to the
// work-in-progress fiber.
let currentHook: Hook | null = null;
let workInProgressHook: Hook | null = null;

function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderExpirationTime: ExpirationTime,
): any {
renderExpirationTime = nextRenderExpirationTime;
currentlyRenderingFiber = workInProgress;

workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.expirationTime = NoWork;

// The following should have already been reset
// currentHook = null;
// workInProgressHook = null;

// didScheduleRenderPhaseUpdate = false;

// TODO Warn if no hooks are used at all during mount, then some are used during update.
// Currently we will identify the update render as a mount because memoizedState === null.
// This is tricky because it's valid for certain types of components (e.g. React.lazy)

// Using memoizedState to differentiate between mount/update only works if at least one stateful hook is used.
// Non-stateful hooks (e.g. context) don't get added to memoizedState,
// so memoizedState would be null during updates and mounts.

// ReactCurrentDispatcher用来Keeps track of the current dispatcher.
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;

let children = Component(props, secondArg);

// Check if there was a render phase update
if (didScheduleRenderPhaseUpdateDuringThisPass) {
// Keep rendering in a loop for as long as render phase updates continue to
// be scheduled. Use a counter to prevent infinite loops.
let numberOfReRenders: number = 0;
do {
didScheduleRenderPhaseUpdateDuringThisPass = false;
invariant(
numberOfReRenders < RE_RENDER_LIMIT,
'Too many re-renders. React limits the number of renders to prevent ' +
'an infinite loop.',
);

numberOfReRenders += 1;

// Start over from the beginning of the list
currentHook = null;
workInProgressHook = null;

workInProgress.updateQueue = null;

ReactCurrentDispatcher.current = __DEV__
? HooksDispatcherOnRerenderInDEV
: HooksDispatcherOnRerender;

children = Component(props, secondArg);
} while (didScheduleRenderPhaseUpdateDuringThisPass);
}

// We can assume the previous dispatcher is always this one, since we set it
// at the beginning of the render phase and there's no re-entrancy.
ReactCurrentDispatcher.current = ContextOnlyDispatcher;

// This check uses currentHook so that it works the same in DEV and prod bundles.
// hookTypesDev could catch more cases (e.g. context) but only in DEV bundles.
const didRenderTooFewHooks =
currentHook !== null && currentHook.next !== null;

renderExpirationTime = NoWork;
currentlyRenderingFiber = (null: any);

currentHook = null;
workInProgressHook = null;

didScheduleRenderPhaseUpdate = false;

invariant(
!didRenderTooFewHooks,
'Rendered fewer hooks than expected. This may be caused by an accidental ' +
'early return statement.',
);

return children;
}
  1. updateHostComponent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
// rootInstanceStackCursor.current = div#root
function pushHostContext(fiber: Fiber): void {
const rootInstance: Container = requiredContext( // div#root
rootInstanceStackCursor.current,
);
const context: HostContext = requiredContext(contextStackCursor.current);
const nextContext = getChildHostContext(context, fiber.type, rootInstance);

// Don't push this Fiber's context unless it's unique.
if (context === nextContext) {
return;
}

// Track the context and the Fiber that provided it.
// This enables us to pop only Fibers that provide unique contexts.
push(contextFiberStackCursor, fiber, fiber);
push(contextStackCursor, nextContext, fiber);
}
function updateHostComponent(current, workInProgress, renderExpirationTime) {
pushHostContext(workInProgress);

if (current === null) {
tryToClaimNextHydratableInstance(workInProgress);
}

const type = workInProgress.type;
const nextProps = workInProgress.pendingProps;
const prevProps = current !== null ? current.memoizedProps : null;

let nextChildren = nextProps.children;
const isDirectTextChild = shouldSetTextContent(type, nextProps);

if (isDirectTextChild) {
// We special case a direct text child of a host node. This is a common
// case. We won't handle it as a reified child. We will instead handle
// this in the host environment that also has access to this prop. That
// avoids allocating another HostText fiber and traversing it.
nextChildren = null;
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
// If we're switching from a direct text child to a normal child, or to
// empty, we need to schedule the text content to be reset.
workInProgress.effectTag |= ContentReset;
}

markRef(current, workInProgress);

// Check the host config to see if the children are offscreen/hidden.
if (
workInProgress.mode & ConcurrentMode &&
renderExpirationTime !== Never &&
shouldDeprioritizeSubtree(type, nextProps)
) {
if (enableSchedulerTracing) {
markSpawnedWork(Never);
}
// Schedule this fiber to re-render at offscreen priority. Then bailout.
workInProgress.expirationTime = workInProgress.childExpirationTime = Never;
// We should never render the children of a dehydrated boundary until we
// upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
return null;
}

reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
return workInProgress.child;
}
  1. reconsileChildrenArray
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
expirationTime: ExpirationTime,
): Fiber | null {
// This algorithm can't optimize by searching from both ends since we
// don't have backpointers on fibers. I'm trying to see how far we can get
// with that model. If it ends up not being worth the tradeoffs, we can
// add it later.

// Even with a two ended optimization, we'd want to optimize for the case
// where there are few changes and brute force the comparison instead of
// going for the Map. It'd like to explore hitting that path first in
// forward-only mode and only go for the Map once we notice that we need
// lots of look ahead. This doesn't handle reversal as well as two ended
// search but that's unusual. Besides, for the two ended optimization to
// work on Iterables, we'd need to copy the whole set.

// In this first iteration, we'll just live with hitting the bad case
// (adding everything to a Map) in for every insert/move.

// If you change this code, also update reconcileChildrenIterator() which
// uses the same algorithm.

let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;

let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
expirationTime,
);
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}

if (newIdx === newChildren.length) {
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}

if (oldFiber === null) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(
returnFiber,
newChildren[newIdx],
expirationTime,
);
if (newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}

// Add all children to a key map for quick lookups.
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);

// Keep scanning and use the map to restore deleted items as moves.
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
expirationTime,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 通过下面两个分支代码创建数组组件元素的兄弟节点关系
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}

if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
existingChildren.forEach(child => deleteChild(returnFiber, child));
}

return resultingFirstChild;
}
  1. createChild
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
function createChild(
returnFiber: Fiber,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
if (typeof newChild === 'string' || typeof newChild === 'number') {
// Text nodes don't have keys. If the previous node is implicitly keyed
// we can continue to replace it without aborting even if it is not a text
// node.
const created = createFiberFromText(
'' + newChild,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
}

if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
const created = createFiberFromElement(
newChild,
returnFiber.mode,
expirationTime,
);
created.ref = coerceRef(returnFiber, null, newChild);
created.return = returnFiber;
return created;
}
case REACT_PORTAL_TYPE: {
const created = createFiberFromPortal(
newChild,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
}
}

if (isArray(newChild) || getIteratorFn(newChild)) {
const created = createFiberFromFragment(
newChild,
returnFiber.mode,
expirationTime,
null,
);
created.return = returnFiber;
return created;
}

throwOnInvalidObjectType(returnFiber, newChild);
}

return null;
}
  1. placeChild
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
function placeChild(
newFiber: Fiber,
lastPlacedIndex: number,
newIndex: number,
): number {
newFiber.index = newIndex;
if (!shouldTrackSideEffects) {
// Noop.
return lastPlacedIndex;
}
const current = newFiber.alternate;
if (current !== null) {
const oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// This is a move.
newFiber.effectTag = Placement;
return lastPlacedIndex;
} else {
// This item can stay in place.
return oldIndex;
}
} else {
// This is an insertion.
newFiber.effectTag = Placement;
return lastPlacedIndex;
}
}
  1. deleteRemainingChildren
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function deleteRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
): null {
if (!shouldTrackSideEffects) {
// Noop.
return null;
}

// TODO: For the shouldClone case, this could be micro-optimized a bit by
// assuming that after the first child we've already added everything.
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
  1. completeUnitOfWork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
function completeUnitOfWork(unitOfWork: Fiber): void {
// Attempt to complete the current unit of work, then move to the next
// sibling. If there are no more siblings, return to the parent fiber.
let completedWork = unitOfWork;
do {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
const current = completedWork.alternate;
const returnFiber = completedWork.return;

// Check if the work completed or if something threw.
if ((completedWork.effectTag & Incomplete) === NoEffect) {
setCurrentDebugFiberInDEV(completedWork);
let next;
if (
!enableProfilerTimer ||
(completedWork.mode & ProfileMode) === NoMode
) {
next = completeWork(current, completedWork, renderExpirationTime);
} else {
startProfilerTimer(completedWork);
next = completeWork(current, completedWork, renderExpirationTime);
// Update render duration assuming we didn't error.
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);
}
resetCurrentDebugFiberInDEV();

if (next !== null) {
// Completing this fiber spawned new work. Work on that next.
workInProgress = next;
return;
}

resetChildExpirationTime(completedWork);

if (
returnFiber !== null &&
// Do not append effects to parents if a sibling failed to complete
(returnFiber.effectTag & Incomplete) === NoEffect
) {
// Append all the effects of the subtree and this fiber onto the effect
// list of the parent. The completion order of the children affects the
// side-effect order.
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = completedWork.firstEffect;
}
if (completedWork.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.lastEffect;
}

// If this fiber had side-effects, we append it AFTER the children's
// side-effects. We can perform certain side-effects earlier if needed,
// by doing multiple passes over the effect list. We don't want to
// schedule our own side-effect on our own list because if end up
// reusing children we'll schedule this effect onto itself since we're
// at the end.
const effectTag = completedWork.effectTag;

// Skip both NoWork and PerformedWork tags when creating the effect
// list. PerformedWork effect is read by React DevTools but shouldn't be
// committed.
if (effectTag > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
} else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
}
} else {
// This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary,
// capture values if possible.
const next = unwindWork(completedWork, renderExpirationTime);

// Because this fiber did not complete, don't reset its expiration time.

if (next !== null) {
// If completing this work spawned new work, do that next. We'll come
// back here again.
// Since we're restarting, remove anything that is not a host effect
// from the effect tag.
next.effectTag &= HostEffectMask;
workInProgress = next;
return;
}

if (
enableProfilerTimer &&
(completedWork.mode & ProfileMode) !== NoMode
) {
// Record the render duration for the fiber that errored.
stopProfilerTimerIfRunningAndRecordDelta(completedWork, false);

// Include the time spent working on failed children before continuing.
let actualDuration = completedWork.actualDuration;
let child = completedWork.child;
while (child !== null) {
actualDuration += child.actualDuration;
child = child.sibling;
}
completedWork.actualDuration = actualDuration;
}

if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its effect list.
returnFiber.firstEffect = returnFiber.lastEffect = null;
returnFiber.effectTag |= Incomplete;
}
}

const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
workInProgress = siblingFiber;
return;
}
// Otherwise, return to the parent
completedWork = returnFiber;
// Update the next thing we're working on in case something throws.
workInProgress = completedWork;
} while (completedWork !== null);

// We've reached the root.
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
}
  1. completeWork
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
function completeWork(
current: Fiber | null,
workInProgress: Fiber,
renderExpirationTime: ExpirationTime,
): Fiber | null {
const newProps = workInProgress.pendingProps;

switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent: {
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
return null;
}
case HostRoot: {
popHostContainer(workInProgress);
popTopLevelLegacyContextObject(workInProgress);
resetMutableSourceWorkInProgressVersions();
const fiberRoot = (workInProgress.stateNode: FiberRoot);
if (fiberRoot.pendingContext) {
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
if (current === null || current.child === null) {
// If we hydrated, pop so that we can delete any remaining children
// that weren't hydrated.
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
// If we hydrated, then we'll need to schedule an update for
// the commit side-effects on the root.
markUpdate(workInProgress);
} else if (!fiberRoot.hydrate) {
// Schedule an effect to clear this container at the start of the next commit.
// This handles the case of React rendering into a container with previous children.
// It's also safe to do for updates too, because current.child would only be null
// if the previous render was null (so the the container would already be empty).
workInProgress.effectTag |= Snapshot;
}
}
updateHostContainer(workInProgress);
return null;
}
case HostComponent: {
popHostContext(workInProgress);
const rootContainerInstance = getRootHostContainer();
const type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(
current,
workInProgress,
type,
newProps,
rootContainerInstance,
);

if (enableDeprecatedFlareAPI) {
const prevListeners = current.memoizedProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (prevListeners !== nextListeners) {
markUpdate(workInProgress);
}
}

if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
if (!newProps) {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
return null;
}

const currentHostContext = getHostContext();
// TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on whether we want to add them top->down or
// bottom->up. Top->down is faster in IE11.
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
// TODO: Move this and createInstance step into the beginPhase
// to consolidate.
if (
prepareToHydrateHostInstance(
workInProgress,
rootContainerInstance,
currentHostContext,
)
) {
// If changes to the hydrated node need to be applied at the
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
} else {
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);

appendAllChildren(instance, workInProgress, false, false);

// This needs to be set before we mount Flare event listeners
workInProgress.stateNode = instance;

if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}

// Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (
finalizeInitialChildren(
instance,
type,
newProps,
rootContainerInstance,
currentHostContext,
)
) {
markUpdate(workInProgress);
}
}

if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef(workInProgress);
}
}
return null;
}
case HostText: {
const newText = newProps;
if (current && workInProgress.stateNode != null) {
const oldText = current.memoizedProps;
// If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
updateHostText(current, workInProgress, oldText, newText);
} else {
if (typeof newText !== 'string') {
invariant(
workInProgress.stateNode !== null,
'We must have new props for new mounts. This error is likely ' +
'caused by a bug in React. Please file an issue.',
);
// This can happen when we abort work.
}
const rootContainerInstance = getRootHostContainer();
const currentHostContext = getHostContext();
const wasHydrated = popHydrationState(workInProgress);
if (wasHydrated) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
workInProgress.stateNode = createTextInstance(
newText,
rootContainerInstance,
currentHostContext,
workInProgress,
);
}
}
return null;
}
case SuspenseComponent: {
popSuspenseContext(workInProgress);
const nextState: null | SuspenseState = workInProgress.memoizedState;

if (enableSuspenseServerRenderer) {
if (nextState !== null && nextState.dehydrated !== null) {
if (current === null) {
const wasHydrated = popHydrationState(workInProgress);
invariant(
wasHydrated,
'A dehydrated suspense component was completed without a hydrated node. ' +
'This is probably a bug in React.',
);
prepareToHydrateHostSuspenseInstance(workInProgress);
if (enableSchedulerTracing) {
markSpawnedWork(Never);
}
return null;
} else {
// We should never have been in a hydration state if we didn't have a current.
// However, in some of those paths, we might have reentered a hydration state
// and then we might be inside a hydration state. In that case, we'll need to exit out of it.
resetHydrationState();
if ((workInProgress.effectTag & DidCapture) === NoEffect) {
// This boundary did not suspend so it's now hydrated and unsuspended.
workInProgress.memoizedState = null;
}
// If nothing suspended, we need to schedule an effect to mark this boundary
// as having hydrated so events know that they're free to be invoked.
// It's also a signal to replay events and the suspense callback.
// If something suspended, schedule an effect to attach retry listeners.
// So we might as well always mark this.
workInProgress.effectTag |= Update;
return null;
}
}
}

if ((workInProgress.effectTag & DidCapture) !== NoEffect) {
// Something suspended. Re-render with the fallback children.
workInProgress.expirationTime = renderExpirationTime;
if (
enableProfilerTimer &&
(workInProgress.mode & ProfileMode) !== NoMode
) {
transferActualDuration(workInProgress);
}
// Do not reset the effect list.
return workInProgress;
}

const nextDidTimeout = nextState !== null;
let prevDidTimeout = false;
if (current === null) {
if (workInProgress.memoizedProps.fallback !== undefined) {
popHydrationState(workInProgress);
}
} else {
const prevState: null | SuspenseState = current.memoizedState;
prevDidTimeout = prevState !== null;
if (!nextDidTimeout && prevState !== null) {
// We just switched from the fallback to the normal children.
// Delete the fallback.
// TODO: Would it be better to store the fallback fragment on
// the stateNode during the begin phase?
const currentFallbackChild: Fiber | null = (current.child: any)
.sibling;
if (currentFallbackChild !== null) {
// Deletions go at the beginning of the return fiber's effect list
const first = workInProgress.firstEffect;
if (first !== null) {
workInProgress.firstEffect = currentFallbackChild;
currentFallbackChild.nextEffect = first;
} else {
workInProgress.firstEffect = workInProgress.lastEffect = currentFallbackChild;
currentFallbackChild.nextEffect = null;
}
currentFallbackChild.effectTag = Deletion;
}
}
}

if (nextDidTimeout && !prevDidTimeout) {
// If this subtreee is running in blocking mode we can suspend,
// otherwise we won't suspend.
// TODO: This will still suspend a synchronous tree if anything
// in the concurrent tree already suspended during this render.
// This is a known bug.
if ((workInProgress.mode & BlockingMode) !== NoMode) {
// TODO: Move this back to throwException because this is too late
// if this is a large tree which is common for initial loads. We
// don't know if we should restart a render or not until we get
// this marker, and this is too late.
// If this render already had a ping or lower pri updates,
// and this is the first time we know we're going to suspend we
// should be able to immediately restart from within throwException.
const hasInvisibleChildContext =
current === null &&
workInProgress.memoizedProps.unstable_avoidThisFallback !== true;
if (
hasInvisibleChildContext ||
hasSuspenseContext(
suspenseStackCursor.current,
(InvisibleParentSuspenseContext: SuspenseContext),
)
) {
// If this was in an invisible tree or a new render, then showing
// this boundary is ok.
renderDidSuspend();
} else {
// Otherwise, we're going to have to hide content so we should
// suspend for longer if possible.
renderDidSuspendDelayIfPossible();
}
}
}

if (supportsPersistence) {
// TODO: Only schedule updates if not prevDidTimeout.
if (nextDidTimeout) {
// If this boundary just timed out, schedule an effect to attach a
// retry listener to the promise. This flag is also used to hide the
// primary children.
workInProgress.effectTag |= Update;
}
}
if (supportsMutation) {
// TODO: Only schedule updates if these values are non equal, i.e. it changed.
if (nextDidTimeout || prevDidTimeout) {
// If this boundary just timed out, schedule an effect to attach a
// retry listener to the promise. This flag is also used to hide the
// primary children. In mutation mode, we also need the flag to
// *unhide* children that were previously hidden, so check if this
// is currently timed out, too.
workInProgress.effectTag |= Update;
}
}
if (
enableSuspenseCallback &&
workInProgress.updateQueue !== null &&
workInProgress.memoizedProps.suspenseCallback != null
) {
// Always notify the callback
workInProgress.effectTag |= Update;
}
return null;
}
case HostPortal:
popHostContainer(workInProgress);
updateHostContainer(workInProgress);
if (current === null) {
preparePortalMount(workInProgress.stateNode.containerInfo);
}
return null;
case ContextProvider:
// Pop provider fiber
popProvider(workInProgress);
return null;
case IncompleteClassComponent: {
// Same as class component case. I put it down here so that the tags are
// sequential to ensure this switch is compiled to a jump table.
const Component = workInProgress.type;
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
return null;
}
case SuspenseListComponent: {
popSuspenseContext(workInProgress);

const renderState: null | SuspenseListRenderState =
workInProgress.memoizedState;

if (renderState === null) {
// We're running in the default, "independent" mode.
// We don't do anything in this mode.
return null;
}

let didSuspendAlready =
(workInProgress.effectTag & DidCapture) !== NoEffect;

const renderedTail = renderState.rendering;
if (renderedTail === null) {
// We just rendered the head.
if (!didSuspendAlready) {
// This is the first pass. We need to figure out if anything is still
// suspended in the rendered set.

// If new content unsuspended, but there's still some content that
// didn't. Then we need to do a second pass that forces everything
// to keep showing their fallbacks.

// We might be suspended if something in this render pass suspended, or
// something in the previous committed pass suspended. Otherwise,
// there's no chance so we can skip the expensive call to
// findFirstSuspended.
const cannotBeSuspended =
renderHasNotSuspendedYet() &&
(current === null || (current.effectTag & DidCapture) === NoEffect);
if (!cannotBeSuspended) {
let row = workInProgress.child;
while (row !== null) {
const suspended = findFirstSuspended(row);
if (suspended !== null) {
didSuspendAlready = true;
workInProgress.effectTag |= DidCapture;
cutOffTailIfNeeded(renderState, false);

// If this is a newly suspended tree, it might not get committed as
// part of the second pass. In that case nothing will subscribe to
// its thennables. Instead, we'll transfer its thennables to the
// SuspenseList so that it can retry if they resolve.
// There might be multiple of these in the list but since we're
// going to wait for all of them anyway, it doesn't really matter
// which ones gets to ping. In theory we could get clever and keep
// track of how many dependencies remain but it gets tricky because
// in the meantime, we can add/remove/change items and dependencies.
// We might bail out of the loop before finding any but that
// doesn't matter since that means that the other boundaries that
// we did find already has their listeners attached.
const newThennables = suspended.updateQueue;
if (newThennables !== null) {
workInProgress.updateQueue = newThennables;
workInProgress.effectTag |= Update;
}

// Rerender the whole list, but this time, we'll force fallbacks
// to stay in place.
// Reset the effect list before doing the second pass since that's now invalid.
if (renderState.lastEffect === null) {
workInProgress.firstEffect = null;
}
workInProgress.lastEffect = renderState.lastEffect;
// Reset the child fibers to their original state.
resetChildFibers(workInProgress, renderExpirationTime);

// Set up the Suspense Context to force suspense and immediately
// rerender the children.
pushSuspenseContext(
workInProgress,
setShallowSuspenseContext(
suspenseStackCursor.current,
ForceSuspenseFallback,
),
);

return workInProgress.child;
}
row = row.sibling;
}
}
} else {
cutOffTailIfNeeded(renderState, false);
}
// Next we're going to render the tail.
} else {
// Append the rendered row to the child list.
if (!didSuspendAlready) {
const suspended = findFirstSuspended(renderedTail);
if (suspended !== null) {
workInProgress.effectTag |= DidCapture;
didSuspendAlready = true;

// Ensure we transfer the update queue to the parent so that it doesn't
// get lost if this row ends up dropped during a second pass.
const newThennables = suspended.updateQueue;
if (newThennables !== null) {
workInProgress.updateQueue = newThennables;
workInProgress.effectTag |= Update;
}

cutOffTailIfNeeded(renderState, true);
// This might have been modified.
if (
renderState.tail === null &&
renderState.tailMode === 'hidden' &&
!renderedTail.alternate &&
!getIsHydrating() // We don't cut it if we're hydrating.
) {
// We need to delete the row we just rendered.
// Reset the effect list to what it was before we rendered this
// child. The nested children have already appended themselves.
const lastEffect = (workInProgress.lastEffect =
renderState.lastEffect);
// Remove any effects that were appended after this point.
if (lastEffect !== null) {
lastEffect.nextEffect = null;
}
// We're done.
return null;
}
} else if (
// The time it took to render last row is greater than time until
// the expiration.
now() * 2 - renderState.renderingStartTime >
renderState.tailExpiration &&
renderExpirationTime > Never
) {
// We have now passed our CPU deadline and we'll just give up further
// attempts to render the main content and only render fallbacks.
// The assumption is that this is usually faster.
workInProgress.effectTag |= DidCapture;
didSuspendAlready = true;

cutOffTailIfNeeded(renderState, false);

// Since nothing actually suspended, there will nothing to ping this
// to get it started back up to attempt the next item. If we can show
// them, then they really have the same priority as this render.
// So we'll pick it back up the very next render pass once we've had
// an opportunity to yield for paint.

const nextPriority = renderExpirationTime - 1;
workInProgress.expirationTime = workInProgress.childExpirationTime = nextPriority;
if (enableSchedulerTracing) {
markSpawnedWork(nextPriority);
}
}
}
if (renderState.isBackwards) {
// The effect list of the backwards tail will have been added
// to the end. This breaks the guarantee that life-cycles fire in
// sibling order but that isn't a strong guarantee promised by React.
// Especially since these might also just pop in during future commits.
// Append to the beginning of the list.
renderedTail.sibling = workInProgress.child;
workInProgress.child = renderedTail;
} else {
const previousSibling = renderState.last;
if (previousSibling !== null) {
previousSibling.sibling = renderedTail;
} else {
workInProgress.child = renderedTail;
}
renderState.last = renderedTail;
}
}

if (renderState.tail !== null) {
// We still have tail rows to render.
if (renderState.tailExpiration === 0) {
// Heuristic for how long we're willing to spend rendering rows
// until we just give up and show what we have so far.
const TAIL_EXPIRATION_TIMEOUT_MS = 500;
renderState.tailExpiration = now() + TAIL_EXPIRATION_TIMEOUT_MS;
// TODO: This is meant to mimic the train model or JND but this
// is a per component value. It should really be since the start
// of the total render or last commit. Consider using something like
// globalMostRecentFallbackTime. That doesn't account for being
// suspended for part of the time or when it's a new render.
// It should probably use a global start time value instead.
}
// Pop a row.
const next = renderState.tail;
renderState.rendering = next;
renderState.tail = next.sibling;
renderState.lastEffect = workInProgress.lastEffect;
renderState.renderingStartTime = now();
next.sibling = null;

// Restore the context.
// TODO: We can probably just avoid popping it instead and only
// setting it the first time we go from not suspended to suspended.
let suspenseContext = suspenseStackCursor.current;
if (didSuspendAlready) {
suspenseContext = setShallowSuspenseContext(
suspenseContext,
ForceSuspenseFallback,
);
} else {
suspenseContext = setDefaultShallowSuspenseContext(suspenseContext);
}
pushSuspenseContext(workInProgress, suspenseContext);
// Do a pass over the next row.
return next;
}
return null;
}
case FundamentalComponent: {
if (enableFundamentalAPI) {
const fundamentalImpl = workInProgress.type.impl;
let fundamentalInstance: ReactFundamentalComponentInstance<
any,
any,
> | null = workInProgress.stateNode;

if (fundamentalInstance === null) {
const getInitialState = fundamentalImpl.getInitialState;
let fundamentalState;
if (getInitialState !== undefined) {
fundamentalState = getInitialState(newProps);
}
fundamentalInstance = workInProgress.stateNode = createFundamentalStateInstance(
workInProgress,
newProps,
fundamentalImpl,
fundamentalState || {},
);
const instance = ((getFundamentalComponentInstance(
fundamentalInstance,
): any): Instance);
fundamentalInstance.instance = instance;
if (fundamentalImpl.reconcileChildren === false) {
return null;
}
appendAllChildren(instance, workInProgress, false, false);
mountFundamentalComponent(fundamentalInstance);
} else {
// We fire update in commit phase
const prevProps = fundamentalInstance.props;
fundamentalInstance.prevProps = prevProps;
fundamentalInstance.props = newProps;
fundamentalInstance.currentFiber = workInProgress;
if (supportsPersistence) {
const instance = cloneFundamentalInstance(fundamentalInstance);
fundamentalInstance.instance = instance;
appendAllChildren(instance, workInProgress, false, false);
}
const shouldUpdate = shouldUpdateFundamentalComponent(
fundamentalInstance,
);
if (shouldUpdate) {
markUpdate(workInProgress);
}
}
return null;
}
break;
}
case ScopeComponent: {
if (enableScopeAPI) {
if (current === null) {
const scopeInstance: ReactScopeInstance = createScopeInstance();
workInProgress.stateNode = scopeInstance;
if (enableDeprecatedFlareAPI) {
const listeners = newProps.DEPRECATED_flareListeners;
if (listeners != null) {
const rootContainerInstance = getRootHostContainer();
updateDeprecatedEventListeners(
listeners,
workInProgress,
rootContainerInstance,
);
}
}
prepareScopeUpdate(scopeInstance, workInProgress);
if (workInProgress.ref !== null) {
markRef(workInProgress);
markUpdate(workInProgress);
}
} else {
if (enableDeprecatedFlareAPI) {
const prevListeners =
current.memoizedProps.DEPRECATED_flareListeners;
const nextListeners = newProps.DEPRECATED_flareListeners;
if (
prevListeners !== nextListeners ||
workInProgress.ref !== null
) {
markUpdate(workInProgress);
}
} else {
if (workInProgress.ref !== null) {
markUpdate(workInProgress);
}
}
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
}
return null;
}
break;
}
case Block:
if (enableBlocksAPI) {
return null;
}
break;
}
invariant(
false,
'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
'React. Please file an issue.',
workInProgress.tag,
);
}
  1. createInstance
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export function createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
): Instance {
let parentNamespace: string;
parentNamespace = ((hostContext: any): HostContextProd);
const domElement: Instance = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
precacheFiberNode(internalInstanceHandle, domElement); // 这是一个带有副作用的函数
updateFiberProps(domElement, props); // 也是一个带有副作用的函数
return domElement;
}
  1. appendChildren
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
appendAllChildren = function(
parent: Instance,
workInProgress: Fiber,
needsVisibilityToggle: boolean,
isHidden: boolean,
) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
let node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
appendInitialChild(parent, node.stateNode.instance);
} else if (node.tag === HostPortal) {
// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
};
  1. appendInitialChild
1
2
3
4
5
6
export function appendInitialChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
}

游离的dom元素是通过这个函数建立了层级关系,在后面最外层div一次append进root中,完成页面的渲染


workLoopSync中的循环

  1. container参与的过程

函数调用过程:

  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> beginWork;
  beginWork --> updateHostRoot;
  updateHostRoot --> cloneUpdateQueue;
  updateHostRoot --> processUpdateQueue;
  processUpdateQueue --> markRenderEventTimeAndConfig;
  processUpdateQueue --> getStateFromUpdate;
  updateHostRoot --> reconcileChildren;
  reconcileChildren --> reconcileChildFibers;
  reconcileChildFibers --> placeSingleChild;
  placeSingleChild --> reconcileSingleElement;
  reconcileSingleElement --> createFiberFromElement;
  createFiberFromElement --> createFiberFromTypeAndProps;
  reconcileSingleElement --> coerceRef;

该过程执行结果:

到这里拿到了最外层组件对应的Fiber对象(performUnitOfWork函数中的next),其实例为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
actualDuration:0,
actualStartTime:-1,
alternate: null,
child: null,
childExpirationTime: 0,
dependencies_old: null,
effectTag: 2,
elementType: Symbol(react.strict_mode),
expirationTime: 1073741823,
firstEffect: null,
index: 0,
key: null,
lastEffect: null,
memoizedProps: null,
memoizedState: null,
mode: 1,
nextEffect: null,
pendingProps: {
children: {
$$typeof: Symbol(react.element),
key: null,
props: {},
ref: null,
type: f App(),
_owner: null
}
},
ref: null,
return: RootFiber,
selfBaseDuration: 0,
sibling: null,
stateNode: null,
tag: 8,
treeBaseDuration: 0,
type: Symbol(react.strict_mode),
updateQueue: null,
}

RootFiber:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
{
actualDuration: 0,
actualStartTime: -1,
child: null,
childExpirationTime: 0,
dependencies_old: null,
alternate: ,
effectTag: 0,
elementType: null,
expirationTime: 1073741823,
firstEffect: null,
index: 0,
key: null,
lastEffect: null,
memoizedProps: null,
memoizedState: null,
mode: 0,
nextEffect: null,
pendingProps: null,
ref: null,
return: null,
selfBaseDuration: 0,
sibling: null,
tag: 3,
treeBaseDuration: 0,
type: null,
stateNode: FiberRoot,
updateQueue: {
baseState: null,
effects: null,
shared: { pending: null },
firstBaseUpdate: {
callback: null,
expirationTime: 1073741823,
next: null,
payload: {
element: {
$$typeof: Symbol(react.element),
key: null,
props: {
children: {
$$typeof: Symbol(react.element)
key: null
props: {}
ref: null
type: ƒ App()
_owner: null
}
},
ref: null
type: Symbol(react.strict_mode)
_owner: null
}
}
suspenseConfig: null,
tag: 0,
},
lastBaseUpdate: {
callback: null,
expirationTime: 1073741823,
next: null,
payload: {
element: {
$$typeof: Symbol(react.element)
key: null
props: {
children: {
$$typeof: Symbol(react.element)
key: null
props: {}
ref: null
type: ƒ App()
_owner: null
}
}
ref: null
type: Symbol(react.strict_mode)
_owner: null
}
},
suspenseConfig: null,
tag: 0,
}
},
}

FiberRoot:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
callbackNode: null
callbackPriority_old: 90
containerInfo: div#root
context: {}
current: RootFiber
finishedExpirationTime: 0
finishedWork: null
firstPendingTime: 1073741823
firstSuspendedTime: 0
hydrate: false
interactionThreadID: 1
lastExpiredTime: 0
lastPendingTime: 1073741823
lastPingedTime: 0
lastSuspendedTime: 0
memoizedInteractions: Set(0) {}
mutableSourceEagerHydrationData: null
mutableSourceLastPendingUpdateTime: 0
nextKnownPendingLevel: 0
pendingChildren: null
pendingContext: null
pendingInteractionMap_old: Map(0) {}
pingCache: null
tag: 0
timeoutHandle: -1
}

WorkInProgress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
{
actualDuration: 0
actualStartTime: -1
alternate: RootFiber
child: next
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: null
expirationTime: 0
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: null
memoizedState: {
element: {
$$typeof: Symbol(react.element)
key: null
props: {
children: {
$$typeof: Symbol(react.element)
key: null
props: {}
ref: null
type: ƒ App()
_owner: null
}
}
ref: null
type: Symbol(react.strict_mode)
_owner: null
}
}
mode: 0
nextEffect: null
pendingProps: null
ref: null
return: null
selfBaseDuration: 0
sibling: null
stateNode: FiberRoot
tag: 3
treeBaseDuration: 0
type: null
updateQueue: {
baseState: {
element: {
$$typeof: Symbol(react.element)
key: null
props: {
children: {
$$typeof: Symbol(react.element)
key: null
props: {}
ref: null
type: ƒ App()
_owner: null
}
}
ref: null
type: Symbol(react.strict_mode)
_owner: null
}
}
effects: null
firstBaseUpdate: null
lastBaseUpdate: null
shared: {pending: null}
}
}
  1. <React.StrictMode> <App /> </React.StrictMode>参与的循环过程

函数调用过程:

  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> beginWork;
  beginWork --> updateMode;
  updateMode --> reconcileChildren;
  reconcileChildren --> mountChildFibers;
  mountChildFibers --> placeSingleChild;
  placeSingleChild --> reconcileSingleElement;
  reconcileSingleElement --> createFiberFromElement;
  createFiberFromElement --> createFiberFromTypeAndProps;
  reconcileSingleElement --> coerceRef;

进行到updateMode --> reconcileChildren这一步的时候,当前的workInProgress(next)的child为null,但pendingProps.children中携带了子元素,用于child的构建(感觉pendingProps这个属性使用来存储后续更新用的)

mountChildFibers该函数同reconcileChildFibers,只不过其中的shouldTrackSideEffects = false

$$typeof用于在诸如reconcileChildFibers函数中进行分支判断

本次循环得到的结果:

下一层的Fiber,next的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: ƒ App()
expirationTime: 1073741823
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: null
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {}
ref: null
return: 本次的workInProgress
selfBaseDuration: 0
sibling: null
stateNode: null
tag: 2
treeBaseDuration: 0
type: ƒ App()
updateQueue: null
}

本次workInProgress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: 本次的next
childExpirationTime: 0
dependencies_old: null
effectTag: 2
elementType: Symbol(react.strict_mode)
expirationTime: 0
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: {
children: {
$$typeof: Symbol(react.element)
key: null
props: {}
ref: null
type: ƒ App()
_owner: null
}
}
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {
children: {
$$typeof: Symbol(react.element)
key: null
props: {}
ref: null
type: ƒ App()
_owner: null
}
}
ref: null
return: RootFiber对应的workInProgress(container循环那层形成的workInProgress)
selfBaseDuration: 0
sibling: null
stateNode: null
tag: 8
treeBaseDuration: 0
type: Symbol(react.strict_mode)
updateQueue: null
}

整个迭代过程中使用了全局变量workInProgress,当前的workInProgress会作为新建的fiber的return属性的值引用存在。

在迭代过程中只产生了组件对应的Fiber,并没有创建其对应的alternate

每次处理前的WorkInProgress的expirationTime都是1073741823,在开始处理时,设置为0,取消了priority。

自定义组件会生成一个对应的Fiber

  1. App函数的处理过程

函数调用过程:

  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> beginWork;
  beginWork --> mountIndeterminateComponent;
  mountIndeterminateComponent --> getUnmaskedContext;
  mountIndeterminateComponent --> getMaskedContext;
  mountIndeterminateComponent --> renderWithHooks;
  mountIndeterminateComponent --> reconcileChildren;
  reconcileChildren --> mountChildFibers;
  mountChildFibers --> placeSingleChild;
  placeSingleChild --> reconcileSingleElement;
  reconcileSingleElement --> createFiberFromElement;
  createFiberFromElement --> createFiberFromTypeAndProps;
  reconcileSingleElement --> coerceRef;

在组件转换为ReactElement的过程中,好像是从内层像外层进行转换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
test
</a>
</header>
</div>
);
}

转换完的结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{
$$typeof: Symbol(react.element)
key: null
props: {
children: {
$$typeof: Symbol(react.element)
key: null
props: {
className: "App-header",
children: [
{
$$typeof: Symbol(react.element)
key: null
props: {src: "/static/media/logo.5d5d9eef.svg", className: "App-logo", alt: "logo"}
ref: null
type: "img"
_owner: null
}, {
$$typeof: Symbol(react.element)
key: null
props: {
children: [
"Edit ",
{
$$typeof: Symbol(react.element),
type: "code",
key: null,
ref: null,
_owner: null,
props: {children: "src/App.js"}
},
" and save to reload."
]
}
ref: null
type: "p"
}, {
$$typeof: Symbol(react.element)
key: null
props: {
className: "App-link", href: "https://reactjs.org", target: "_blank", rel: "noopener noreferrer", children: "test"
}
ref: null
type: "a"
_owner: null
}
]
}
ref: null
type: "header"
_owner: null
}
className: "App"
}
ref: null
type: "div"
_owner: null
}

mountIndeterminateComponent函数中进一步区分是函数组件还是类组件

本次循环得到的结果:

下一层的Fiber

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "div"
expirationTime: 1073741823
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: null
memoizedState: null
mode: 1
nextEffect: null
pendingProps: 上面解析后的reactElement
ref: null
return: 本次的workInProgress
selfBaseDuration: 0
sibling: null
stateNode: null
tag: 5
treeBaseDuration: 0
type: "div"
updateQueue: null
}

本次workInProgress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: 上面新生成的Fiber
childExpirationTime: 0
dependencies_old: null
effectTag: 1
elementType: ƒ App()
expirationTime: 0
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: {}
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {}
ref: null
return: 上上面的workInProgress
selfBaseDuration: 0
sibling: null
stateNode: null
tag: 0
treeBaseDuration: 0
type: ƒ App()
updateQueue: null
}
  1. <div className="App">参与的循环过程
  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> beginWork;
  beginWork --> updateHostComponent;
  updateHostComponent --> reconcileChildren;
  reconcileChildren --> mountChildFibers;
  mountChildFibers --> placeSingleChild;
  placeSingleChild --> reconcileSingleElement;
  reconcileSingleElement --> createFiberFromElement;
  createFiberFromElement --> createFiberFromTypeAndProps;
  reconcileSingleElement --> coerceRef;

本次循环得到的结果:

下一层的Fiber:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "header"
expirationTime: 1073741823
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: null
memoizedState: null
mode: 1
nextEffect: null
pendingProps: 上面ReactElement下面一层
ref: null
return: 上面的workInProgress
selfBaseDuration: 0
sibling: null
stateNode: null
tag: 5
treeBaseDuration: 0
type: "header"
updateQueue: null
}

本次的workInProgress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: 上面的Fiber
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "div"
expirationTime: 0
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: {
className: "App",
children: {
$$typeof: Symbol(react.element)
key: null
props: {className: "App-header", children: Array(3)}
ref: null
type: "header"
_owner: null
}
}
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {
className: "App",
children: {
$$typeof: Symbol(react.element)
key: null
props: {className: "App-header", children: Array(3)}
ref: null
type: "header"
_owner: null
}
}
ref: null
return: FiberNode {tag: 0, key: null, stateNode: null, elementType: ƒ, type: ƒ, …}
selfBaseDuration: 0
sibling: null
stateNode: null
tag: 5
treeBaseDuration: 0
type: "div"
updateQueue: null
}
  1. <header className="App-header">参与的循环过程
  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> beginWork;
  beginWork --> updateHostComponent;
  updateHostComponent --> reconcileChildren;
  reconcileChildren --> mountChildFibers;
  mountChildFibers --> reconcileChildrenArray;
  reconcileChildrenArray --> createChild;
  reconcileChildrenArray --> placeChild;

第一次是处理<img />标签,后续函数调用为:

  graph TD;

  createChild --> createFiberFromElement;
  createFiberFromElement --> createFiberFromTypeAndProps;
  reconcileSingleElement --> coerceRef;

<img />标签对应的fiber对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "img"
expirationTime: 1073741823
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: null
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {src: "/static/media/logo.5d5d9eef.svg", className: "App-logo", alt: "logo"}
ref: null
return: 上面的workInProgress
selfBaseDuration: 0
sibling: 下一个Fiber
stateNode: null
tag: 5
treeBaseDuration: 0
type: "img"
updateQueue: null
}

在处理这个fiber的时候,用到了index属性

第二次处理p标签,

本次生成的Fiber:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "p"
expirationTime: 1073741823
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: null
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {
children: {
"Edit ",
{
$$typeof: Symbol(react.element)
key: null
props: {children: "src/App.js"}
ref: null
type: "code"
_owner: null
},
" and save to reload."
}
}
ref: null
return: 上一个workInProgerss
selfBaseDuration: 0
sibling: 下面的Fiber实例
stateNode: null
tag: 5
treeBaseDuration: 0
type: "p"
updateQueue: null
}

本次执行完毕,上一次生成的Fiber的sibling设为本次的P标签对应的Fiber

第三次处理a标签

本次生成的Fiber:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "a"
expirationTime: 1073741823
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: null
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {
children: "test"
className: "App-link"
href: "https://reactjs.org"
rel: "noopener noreferrer"
target: "_blank"
}
ref: null
return: 上次的workInProgress
selfBaseDuration: 0
sibling: null
stateNode: null
tag: 5
treeBaseDuration: 0
type: "a"
updateQueue: null
}

本次循环执行完成上面的p标签对应的sibling指向了本次生成的Fiber实例

至此,本次的workInProgress的循环结束,此时workInProgress为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: 上面img标签对应的Fiber
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "header"
expirationTime: 0
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: {className: "App-header", children: Array(3)}
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {className: "App-header", children: Array(3)}
ref: null
return: 外面一层div对应的Fiber
selfBaseDuration: 0
sibling: null
stateNode: null
tag: 5
treeBaseDuration: 0
type: "header"
updateQueue: null
}
  1. img标签对应的Fiber作为workInProgress参与循环

本次循环的特殊之处在于循环至此第一次出现child为undefined的情况,进入performUnitOfWork中调用completeUnitOfWork函数的分支

  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> beginWork;
  beginWork --> updateHostComponent;
  updateHostComponent --> reconcileChildren;
  reconcileChildren --> mountChildFibers;
  mountChildFibers --> deleteRemainingChildren;
  performUnitOfWork --> completeUnitOfWork;
  completeUnitOfWork --> completeWork;
  completeWork --> createInstance;
  completeWork --> appendAllChildren;

本次的workInProgress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "img"
expirationTime: 0
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: {src: "/static/media/logo.5d5d9eef.svg", className: "App-logo", alt: "logo"}
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {src: "/static/media/logo.5d5d9eef.svg", className: "App-logo", alt: "logo"}
ref: null
return: header对应的Fiber
selfBaseDuration: 0
sibling: P标签对应的Fiber
stateNode: null // 注意此处的stateNode为null
tag: 5
treeBaseDuration: 0
type: "img"
updateQueue: null
}

img标签在completeWork函数中,感觉最大的效果是给其对应的Fiber的stateNode添加了指向,同时创建了img标签的dom元素,但并未赋值属性

completeUnitOfWork函数的最后,将img标签对应的Fiber对象的sibling赋值给了workInProgress

  1. img标签的siblingp标签参与的本次循环

本次的workInProgress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "p"
expirationTime: 0
firstEffect: null
index: 1
key: null
lastEffect: null
memoizedProps: null
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {children: Array(3)}
ref: null
return: FiberNode {tag: 5, key: null, elementType: "header", type: "header", stateNode: null, …}
selfBaseDuration: 0
sibling: FiberNode {tag: 5, key: null, elementType: "a", type: "a", stateNode: null, …}
stateNode: null
tag: 5
treeBaseDuration: 0
type: "p"
updateQueue: null
  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> beginWork;
  beginWork --> updateHostComponent;
  updateHostComponent --> reconcileChildren;
  reconcileChildren --> mountChildFibers;
  mountChildFibers --> reconcileChildrenArray;
  reconcileChildrenArray --> createChild;
  reconcileChildrenArray --> placeChild;

第一次是处理字符串"Edit ",后续函数调用为:

  graph TD;

  createChild --> createFiberFromText;
  createFiberFromText --> createFiber;

生成的Fiber为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: null
expirationTime: 1073741823
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: null
memoizedState: null
mode: 1
nextEffect: null
pendingProps: "Edit "
ref: null
return: 上面的workInProgress
selfBaseDuration: 0
sibling: 下面的Fiber
stateNode: null
tag: 6 // 该Fiber的tag为6
treeBaseDuration: 0
type: null
updateQueue: null
}

第二次处理<code>src/App.js</code>

1
2
3
4
5
6
7
8
{
$$typeof: Symbol(react.element)
key: null
props: {children: "src/App.js"}
ref: null
type: "code"
_owner: null
}
  graph TD;

  createChild --> createFiberFromElement;
  createFiberFromText --> createFiber;

生成的Fiber为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "code"
expirationTime: 1073741823
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: null
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {children: "src/App.js"}
ref: null
return: 上面的workInProgress
selfBaseDuration: 0
sibling: 指向下面的Fiber
stateNode: null
tag: 5
treeBaseDuration: 0
type: "code"
updateQueue: null
}

第三次处理字符串" and save to reload"

  graph TD;

  createChild --> createFiberFromElement;
  createFiberFromText --> createFiber;

生成的Fiber:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: null
expirationTime: 1073741823
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: null
memoizedState: null
mode: 1
nextEffect: null
pendingProps: " and save to reload."
ref: null
return: 上面的workInProgress
selfBaseDuration: 0
sibling: null
stateNode: null
tag: 6
treeBaseDuration: 0
type: null
updateQueue: null

文本对应的Fiber,其type为null

本次循环过程中使用的workInProgress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: FiberNode {tag: 6, key: null, elementType: null, type: null, stateNode: null, …}
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "p"
expirationTime: 0
firstEffect: null
index: 1
key: null
lastEffect: null
memoizedProps: {children: Array(3)}
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {children: Array(3)}
ref: null
return: FiberNode {tag: 5, key: null, elementType: "header", type: "header", stateNode: null, …}
selfBaseDuration: 0
sibling: FiberNode {tag: 5, key: null, elementType: "a", type: "a", stateNode: null, …}
stateNode: null
tag: 5
treeBaseDuration: 0
type: "p"
updateQueue: null
}
  1. 上面生成的字符串"Edit "的循环

本次循环的workInProgress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: null
expirationTime: 1073741823
firstEffect: null
index: 0
key: null
lastEffect: null
memoizedProps: null
memoizedState: null
mode: 1
nextEffect: null
pendingProps: "Edit "
ref: null
return: FiberNode {tag: 5, key: null, elementType: "p", type: "p", stateNode: null, …}
selfBaseDuration: 0
sibling: FiberNode {tag: 5, key: null, elementType: "code", type: "code", stateNode: null, …}
stateNode: TextNode
tag: 6
treeBaseDuration: 0
type: null
updateQueue: null
}
  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> beginWork;
  beginWork --> updateHostText;
  performUnitOfWork --> completeUnitOfWork;
  completeUnitOfWork --> completeWork;
  completeWork --> createTextInstance;

将sibling赋值给workInProgress的过程是在completeUnitOfWork函数中完成的

  1. 上面"Edit "字符串对应的Fiber的sibling的循环
  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> beginWork;
  beginWork --> updateHostComponent;
  updateHostComponent --> reconcileChildren;
  1. " and save to reload"的Fiber的循环

本次参与循环的workInProgress

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: null
expirationTime: 0
firstEffect: null
index: 2
key: null
lastEffect: null
memoizedProps: " and save to reload."
memoizedState: null
mode: 1
nextEffect: null
pendingProps: " and save to reload."
ref: null
return: FiberNode {tag: 5, key: null, elementType: "p", type: "p", stateNode: null, …}
selfBaseDuration: 0
sibling: null
stateNode: 下面创建的TextNode
tag: 6
treeBaseDuration: 0
type: null
updateQueue: null
}
  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> completeUnitOfWork;
  completeUnitOfWork --> completeWork;
  completeWork --> createTextInstance;

在上面createTextInstance结束的时候会创建一个TextNode

  1. 包含上面三个元素的p标签参与本次的循环
  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> completeUnitOfWork;
  completeUnitOfWork --> completeWork;
  completeWork --> createInstance;
  createInstance --> createElement;

createElement函数结束的时候会创建一个P标签对应的dom元素

参与本次循环的workInProgress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: FiberNode {tag: 6, key: null, elementType: null, type: null, stateNode: text, …}
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "p"
expirationTime: 0
firstEffect: null
index: 1
key: null
lastEffect: null
memoizedProps: {children: Array(3)}
memoizedState: null
mode: 1
nextEffect: null
pendingProps:
children: (3) ["Edit ", {…}, " and save to reload."]
ref: null
return: FiberNode {tag: 5, key: null, elementType: "header", type: "header", stateNode: null, …}
selfBaseDuration: 0
sibling: FiberNode {tag: 5, key: null, elementType: "a", type: "a", stateNode: null, …}
stateNode: p
tag: 5
treeBaseDuration: 0
type: "p"
updateQueue: null
}

TODO: 这里要捋一下sibling和return的代码细节

  1. a标签参与的循环

本次循环的workInProgress:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
actualDuration: 0
actualStartTime: -1
alternate: null
child: null
childExpirationTime: 0
dependencies_old: null
effectTag: 0
elementType: "a"
expirationTime: 1073741823
firstEffect: null
index: 2
key: null
lastEffect: null
memoizedProps: {className: "App-link", href: "https://reactjs.org", target: "_blank", rel: "noopener noreferrer", children: "test"}
memoizedState: null
mode: 1
nextEffect: null
pendingProps: {className: "App-link", href: "https://reactjs.org", target: "_blank", rel: "noopener noreferrer", children: "test"}
ref: null
return: FiberNode {tag: 5, key: null, elementType: "header", type: "header", stateNode: null, …}
selfBaseDuration: 0
sibling: null
stateNode: a标签节点
tag: 5
treeBaseDuration: 0
type: "a"
updateQueue: null
}
  graph TD;

  workLoopSync --> performUnitOfWork;
  performUnitOfWork --> completeUnitOfWork;
  completeUnitOfWork --> completeWork;
  completeWork --> createInstance;
  createInstance --> createElement;

后续在completeUnitOfWork函数中不断循环(通过returnFiber),逐渐从内循环到外层组件

在完成上面的创建更新的过程之后,执行控制回到performSyncWorkOnRoot函数中,进入commitRoot函数(位于react-reconciler/src/ReactFiberWorkLoop.old的 1056行,可在此处加断点获取此时的root值)

提交root的过程在下一篇继续