Commit Phase Overview
Fiber’s DOM structure and EffectList are also successfully mounted on Fiber after the render phase is completed. After the Render phase is complete, it’s time for Fiber’s commit phase.
function performConcurrentWorkOnRoot(root, didTimeout) {
// Render phase is complete and ready to enter commit phase
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, lanes);
}
function finishConcurrentRender(root, exitStatus, lanes) {
switch (exitStatus) {
case RootCompleted: {
// The work completed. Ready to commit.
commitRoot(root);
break; }}}Copy the code
CommitRoot is the start point of the commit phase, which is synchronous logic and does not use the time sharding function in concurrent mode. The main flow of commitRoot is as follows
Commit phase details
Handles the Effect from the last render
function commitRootImpl(root, renderPriorityLevel) {
do {
flushPassiveEffects();
} while(rootWithPendingPassiveEffects ! = =null);
}
Copy the code
The start time of the COMMIT phase will process the effect asynchronous handlers registered in the last Render phase & which have not yet been completed. The while loop ensures that if a new setState is executed in an asynchronous function, it is completed before the COMMIT.
Mount the asynchronous callback function for Effect
If the Root Fiber subtreeFlags or flags is not NoFlags, the Root Fiber Effect needs to be executed. Execute the flushPassiveEffects to register the asynchronous callback function.
The processing logic for subtreeFlags is in the completeWork phase of the Render phase.
if( (finishedWork.subtreeFlags & PassiveMask) ! == NoFlags || (finishedWork.flags & PassiveMask) ! == NoFlags ) {if(! rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects =true;
pendingPassiveEffectsRemainingLanes = remainingLanes;
scheduleCallback(NormalSchedulerPriority, () = > {
flushPassiveEffects();
return null; }); }}Copy the code
The focus is on the flushPassiveEffects function, which is already called when processing the Effect generated by the last render. Its role is to handle the create/destory logic of useEffect.
export function flushPassiveEffects() :boolean {
return flushPassiveEffectsImpl();
}
function flushPassiveEffectsImpl() {
// Handle the destory function of hook
commitPassiveUnmountEffects(root.current);
// Handle the create function of the hook
commitPassiveMountEffects(root, root.current);
}
Copy the code
CommitPassiveUnmountEffects and commitPassiveMountEffect logical comparison is consistent, depth-first traversal, and at the begin and complete implementation of the different logical, Here commitPassiveUnmountEffects as examples.
function commitPassiveUnmountEffects_begin() {
while(nextEffect ! = =null) {
const fiber = nextEffect;
const child = fiber.child;
if((fiber.subtreeFlags & PassiveMask) ! == NoFlags && child ! = =null) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else{ commitPassiveUnmountEffects_complete(); }}}function commitPassiveUnmountEffects_complete() {
while(nextEffect ! = =null) {
const fiber = nextEffect;
if((fiber.flags & Passive) ! == NoFlags) { commitPassiveUnmountOnFiber(fiber); }const sibling = fiber.sibling;
if(sibling ! = =null) {
nextEffect = sibling;
return; } nextEffect = fiber.return; }}function commitPassiveUnmountOnFiber(finishedWork: Fiber) :void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// Execute 'EffectFlag' as HookPassivecommitHookEffectListUnmount( HookPassive | HookHasEffect, finishedWork, finishedWork.return, ); }}}Copy the code
BeforeMutation phase processing
BeforeMutation is the first stage of the COMMIT process, which is performed before the DOM rendering (Mutation stage) in the browse area. The getSnapshotBeforeUpdate life cycle is processed in this phase.
The starting point of beforeMutation function for commitBeforeMutatuonEffects
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
root,
finishedWork,
);
Copy the code
Alternately with depth traversal commitBeforeMutatuonEffects will from Root Fiber perform the begin and complete function.
Begin: There is no special processing logic.
Complete phase: to perform commitBeforeMutationEffectsOnFiber logic. The deep traversal logic here is very similar to the beginWork/completeWork logic in the Render stage.
export function commitBeforeMutationEffects(root: FiberRoot, firstChild: Fiber,) {... nextEffect = firstChild; commitBeforeMutationEffects_begin(); . }function commitBeforeMutationEffects_begin() {
while(nextEffect ! = =null) {
const fiber = nextEffect;
const child = fiber.child;
if( (fiber.subtreeFlags & BeforeMutationMask) ! == NoFlags && child ! = =null
) {
ensureCorrectReturnPointer(child, fiber);
nextEffect = child;
} else{ commitBeforeMutationEffects_complete(); }}}function commitBeforeMutationEffects_complete() {
while(nextEffect ! = =null) {
const fiber = nextEffect;
commitBeforeMutationEffectsOnFiber(fiber);
const sibling = fiber.sibling;
if(sibling ! = =null) {
nextEffect = sibling;
return; } nextEffect = fiber.return; }}Copy the code
CommitBeforeMutationEffectsOnFiber logic is simpler, determine whether the current Fiber to exist, the Snapshot of the Flag. If so, the corresponding getSnapshotBeforeUpdate function is executed.
function commitBeforeMutationEffectsOnFiber(finishedWork: Fiber) {
const current = finishedWork.alternate;
const flags = finishedWork.flags;
if((flags & Snapshot) ! == NoFlags) {switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
case HostComponent:
case HostText:
case HostPortal:
case IncompleteClassComponent:{
// Nothing to do for these component types
break;
}
case ClassComponent: {
if(current ! = =null) {
const prevProps = current.memoizedProps;
const prevState = current.memoizedState;
const instance = finishedWork.stateNode;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
const snapshot = instance.getSnapshotBeforeUpdate(
finishedWork.elementType === finishedWork.type
? prevProps
: resolveDefaultProps(finishedWork.type, prevProps),
prevState,
);
instance.__reactInternalSnapshotBeforeUpdate = snapshot;
}
break;
}
case HostRoot: {
if (supportsMutation) {
const root = finishedWork.stateNode;
clearContainer(root.containerInfo);
}
break;
}
default: {
throw new Error(
'This unit of work tag should not have side-effects. This error is ' +
'likely caused by a bug in React. Please file an issue.',); }}}}Copy the code
Processing of Mutation stage
BeforeMutation processing is complete, the logic of the Mutation phase is executed. At this stage React will render Fiber from WorkInProgress on the browser.
Like beforeMutation, the starting point of Mutatio is commitMutationEffects
const shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(
root,
finishedWork,
);
// The next phase is the mutation phase, where we mutate the host tree.
commitMutationEffects(root, finishedWork, lanes);
Copy the code
CommitMutationEffects will go through the BEGIN and complete phases in depth.
export function commitMutationEffects(root: FiberRoot, firstChild: Fiber, committedLanes: Lanes,) {
inProgressLanes = committedLanes;
inProgressRoot = root;
nextEffect = firstChild;
commitMutationEffects_begin(root);
inProgressLanes = null;
inProgressRoot = null;
}
function commitMutationEffects_begin(root: FiberRoot) {
while(nextEffect ! = =null) {
const fiber = nextEffect;
// TODO: Should wrap this in flags check, too, as optimization
const deletions = fiber.deletions;
if(deletions ! = =null) {
for (let i = 0; i < deletions.length; i++) {
constchildToDelete = deletions[i]; commitDeletion(root, childToDelete, fiber); }}const child = fiber.child;
if((fiber.subtreeFlags & MutationMask) ! == NoFlags && child ! = =null) {
nextEffect = child;
} else{ commitMutationEffects_complete(root); }}}function commitMutationEffects_complete(root: FiberRoot) {
while(nextEffect ! = =null) {
const fiber = nextEffect;
commitMutationEffectsOnFiber(fiber, root);
const sibling = fiber.sibling;
if(sibling ! = =null) {
nextEffect = sibling;
return; } nextEffect = fiber.return; }}Copy the code
Begin phase: In the Render phase, the Child_Fiber portion is labeled as DELETE, and the corresponding DOM node is deleted at this point. If the Child_Fiber node of the current Fiber does not have flags such as Placement, Update, ChildDeletion, and ContentReset, execute the complete phase.
Complete phase: complete stage will perform commitMutationEffectsOnFiber Fiber processing. According to the different processing of flag for commitMutationEffectsOnFiber stage. Placement, for example, renders Fiber’s corresponding DOM in the browser. Update updates the corresponding DOM node.
function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
const flags = finishedWork.flags;
const primaryFlags = flags & (Placement | Update | Hydrating);
outer: switch (primaryFlags) {
case Placement: {
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(finishedWork);
finishedWork.flags &= ~Placement;
// Update
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break;
}
case Update: {
const current = finishedWork.alternate;
commitWork(current, finishedWork);
break; }}}Copy the code
The commitWork logic above executes the destory function of useLayoutEffect (the return value of useLayoutEffect). The logic is as follows, performs commitHookEffectListUnmount processing all EffectTag HookLayout | HookHasEffect Fiber, perform his destory function.
There appeared a new HookInsertion | HookHasEffect logo, according to a source of speculation is likely to be a new hooks useInsertionEffect
function commitWork(current: Fiber | null, finishedWork: Fiber) :void {
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent: {
commitHookEffectListUnmount(
HookInsertion | HookHasEffect,
finishedWork,
finishedWork.return,
);
commitHookEffectListMount(HookInsertion | HookHasEffect, finishedWork);
commitHookEffectListUnmount(
HookLayout | HookHasEffect,
finishedWork,
finishedWork.return,
);
return;
}
case ClassComponent: {
return;
}
case HostComponent: {
const instance: Instance = finishedWork.stateNode;
if(instance ! =null) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
constoldProps = current ! = =null ? current.memoizedProps : newProps;
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
finishedWork.updateQueue = null;
if(updatePayload ! = =null) {
commitUpdate(
instance,
updatePayload,
type, oldProps, newProps, finishedWork, ); }}return;
}
case HostText: {
const textInstance: TextInstance = finishedWork.stateNode;
const newText: string = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldText: string= current ! = =null ? current.memoizedProps : newText;
commitTextUpdate(textInstance, oldText, newText);
return; }}}function commitHookEffectListUnmount(
flags: HookFlags,
finishedWork: Fiber,
nearestMountedAncestor: Fiber | null.) {
const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any);
constlastEffect = updateQueue ! = =null ? updateQueue.lastEffect : null;
if(lastEffect ! = =null) {
const firstEffect = lastEffect.next;
let effect = firstEffect;
do {
if ((effect.tag & flags) === flags) {
// Unmount
const destroy = effect.destroy;
effect.destroy = undefined;
if(destroy ! = =undefined) {
safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
}
}
effect = effect.next;
} while (effect !== firstEffect);
}
}
Copy the code
Processing of Layout stage
The Layout stage occurs after the browser has rendered, and the main functions include the execution of the Create function for componentDidMount and useLayoutEffect. Also mount the useEffect create/destory asynchronous function.
The layout phase logic starts with commitLayoutEffects and points root’s current to finishedWork(workInProgress) before processing the layout phase logic.
// The work-in-progress tree is now the current tree. This must come after
// the mutation phase, so that the previous tree is still current during
// componentWillUnmount, but before the layout phase, so that the finished
// work is current during componentDidMount/Update.
root.current = finishedWork;
commitLayoutEffects(finishedWork, root, lanes);
Copy the code
Similarly, commitLayoutEffects have two stages, begin and complete.
Begin: There is no special logic
Complete phase: perform commitLayoutEffectOnFiber logic
As an additional mention of OffscreenComponent, this is a new API that will be used to implement keep-alive functionality similar to Vue.
export function commitLayoutEffects(finishedWork: Fiber, root: FiberRoot, committedLanes: Lanes,) :void {
commitLayoutEffects_begin(finishedWork, root, committedLanes);
}
function commitLayoutEffects_begin(subtreeRoot: Fiber, root: FiberRoot, committedLanes: Lanes,) {
// Suspense layout effects semantics don't change for legacy roots.
constisModernRoot = (subtreeRoot.mode & ConcurrentMode) ! == NoMode;while(nextEffect ! = =null) {
const fiber = nextEffect;
const firstChild = fiber.child;
if (
enableSuspenseLayoutEffectSemantics &&
fiber.tag === OffscreenComponent &&
isModernRoot
) {
...
}
if((fiber.subtreeFlags & LayoutMask) ! == NoFlags && firstChild ! = =null) {
ensureCorrectReturnPointer(firstChild, fiber);
nextEffect = firstChild;
} else{ commitLayoutMountEffects_complete(subtreeRoot, root, committedLanes); }}}function commitLayoutMountEffects_complete(subtreeRoot: Fiber, root: FiberRoot, committedLanes: Lanes,) {
while(nextEffect ! = =null) {
const fiber = nextEffect;
if((fiber.flags & LayoutMask) ! == NoFlags) {const current = fiber.alternate;
commitLayoutEffectOnFiber(root, current, fiber, committedLanes);
if (fiber === subtreeRoot) {
nextEffect = null;
return;
}
const sibling = fiber.sibling;
if(sibling ! = =null) {
nextEffect = sibling;
return; } nextEffect = fiber.return; }}Copy the code
CommitLayoutEffectOnFiber performed in Class Component componentDidMount/componentDidUpdate Function and the Function of Component UseLayoutEffect create function in. The following code
function commitLayoutEffectOnFiber(
finishedRoot: FiberRoot,
current: Fiber | null,
finishedWork: Fiber,
committedLanes: Lanes,
) :void {
if((finishedWork.flags & LayoutMask) ! == NoFlags) {switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent: {
// Execute the useLayoutEffect of the function component
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
break;
}
case ClassComponent:
const instance = finishedWork.stateNode;
if (finishedWork.flags & Update) {
if(! offscreenSubtreeWasHidden) {if (current === null) {
// Implement componentDidMount
instance.componentDidMount();
} else {
const prevProps =
finishedWork.elementType === finishedWork.type
? current.memoizedProps
: resolveDefaultProps(
finishedWork.type,
current.memoizedProps,
);
const prevState = current.memoizedState;
// We could update instance props and state here,
// but instead we rely on them being set during last render.
// TODO: revisit this when we implement resuming.
instance.componentDidUpdate(
prevProps,
prevState,
instance.__reactInternalSnapshotBeforeUpdate,
)
}
}
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
// Execute the second argument of setState
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break;
}
case HostRoot: {
// Execute the second argument to render
const updateQueue: UpdateQueue<
*,
> | null = (finishedWork.updateQueue: any);
if(updateQueue ! = =null) {
let instance = null;
if(finishedWork.child ! = =null) {
switch (finishedWork.child.tag) {
case HostComponent:
instance = getPublicInstance(finishedWork.child.stateNode);
break;
case ClassComponent:
instance = finishedWork.child.stateNode;
break;
}
}
commitUpdateQueue(finishedWork, updateQueue, instance);
}
break; }}Copy the code
Review past
- React Principle Analysis (1
- React source code analysis (2) — Fiber render phase
Refer to the article
- Juejin. Cn/post / 691962…
- Juejin. Cn/post / 691779…
- react.iamkasong.com/