At the end of the render phase, commitRoot(root) will be called; enter the commit phase, where the root refers to fiberRoot, and then traverse the effectList generated in the render phase, and the Fiber nodes on the effectList save the corresponding props changes. Afterwards, the effectList will be traversed to perform corresponding dom operations and life cycles, hooks callbacks or destruction functions. The functions of each function are as follows
In the commitRoot function, the commitRootImpl function is actually scheduled
//ReactFiberWorkLoop.old.js function commitRoot(root) { var renderPriorityLevel = getCurrentPriorityLevel(); runWithPriority$1(ImmediatePriority$1, commitRootImpl.bind(null, root, renderPriorityLevel)); return null; }
There are three main parts in the commitRootImpl function:
commit stage pre-work
- Call flushPassiveEffects to execute all effect tasks
- Initialize related variables
- Assign the firstEffect to the later traversal effectList
//ReactFiberWorkLoop.old.js do { // Call flushPassiveEffects to execute all effect tasks flushPassiveEffects(); } while (rootWithPendingPassiveEffects !== null); //... // Reset variable finishedWork to rooFiber root.finishedWork = null; //reset priority root.finishedLanes = NoLanes; // Scheduler callback function reset root.callbackNode = null; root.callbackId = NoLanes; // reset global variable if (root === workInProgressRoot) { workInProgressRoot = null; workInProgress = null; workInProgressRootRenderLanes = NoLanes; } else { } //rootFiber may have new side effects, add it to effectLis as well let firstEffect; if (finishedWork.effectTag > PerformedWork) { if (finishedWork.lastEffect !== null) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { firstEffect = finishedWork.firstEffect; }
mutation stage
Traversing the effectList executes three methods commitBeforeMutationEffects, commitMutationEffects, commitLayoutEffects to execute the corresponding dom operation and life cycle
When introducing the double-cache Fiber tree, after building the workInProgress Fiber tree, we will point the current of fiberRoot to the workInProgress Fiber, so that the workInProgress Fiber becomes current. This step occurs after the commitMutationEffects function is executed and before commitLayoutEffects, because componentWillUnmount occurs in commitMutationEffects In the function, the previous Update can also be obtained at this time, and componentDidMount and componentDidUpdate will be executed in commitLayoutEffects, and the updated real dom can already be obtained at this time
function commitRootImpl(root, renderPriorityLevel) { //... do { //... commitBeforeMutationEffects(); } while (nextEffect !== null); do { //... commitMutationEffects(root, renderPriorityLevel);//commitMutationEffects } while (nextEffect !== null); root.current = finishedWork;//Switch current Fiber tree do { //... commitLayoutEffects(root, lanes);//commitLayoutEffects } while (nextEffect !== null); //... }
after mutation
- Assign related variables according to rootDoesHavePassiveEffects
- Execute flushSyncCallbackQueue to handle life cycles such as componentDidMount or synchronization tasks such as useLayoutEffect
const rootDidHavePassiveEffects = rootDoesHavePassiveEffects; // Assign related variables according to rootDoesHavePassiveEffects if (rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = false; rootWithPendingPassiveEffects = root; pendingPassiveEffectsLanes = lanes; pendingPassiveEffectsRenderPriority = renderPriorityLevel; } else {} //... // make sure to be scheduled ensureRootIsScheduled(root, now()); // ... // Execute flushSyncCallbackQueue to handle life cycles such as componentDidMount or synchronization tasks such as useLayoutEffect flushSyncCallbackQueue(); return null;
Now let's take a look at what the three functions in the mutation stage do
commitBeforeMutationEffects
This function mainly does the following two thingsExecute getSnapshotBeforeUpdate
In the source code, the function corresponding to commitBeforeMutationEffectOnFiber is commitBeforeMutationLifeCycles, which calls getSnapshotBeforeUpdate. Now we know that getSnapshotBeforeUpdate is executed in the commitBeforeMutationEffect function in the mutation phase, and the commit phase is synchronous, so getSnapshotBeforeUpdate is also executed synchronously.
function commitBeforeMutationLifeCycles( current: Fiber | null, finishedWork: Fiber, ): void { switch (finishedWork.tag) { //... case ClassComponent: { if const instance = finishedWork.stateNode; const snapshot = instance.getSnapshotBeforeUpdate(//getSnapshotBeforeUpdate finishedWork.elementType === finishedWork.type ? prevProps : resolveDefaultProps(finishedWork.type, prevProps), prevState, ); } }
- dispatch useEffect
Call flushPassiveEffectsImpl in the flushPassiveEffects function to traverse pendingPassiveEffectsUnmount and pendingPassiveHookEffectsMount, execute the corresponding effect callback and destruction function, and these two arrays are assigned in the commitLayoutEffects function (will be mentioned later), after the mutation, the effectList is assigned to rootWithPendingPassiveedCall, and then backsch Scheduling the execution of flushPassiveEffects
Related reference video explanation: enter study
function flushPassiveEffectsImpl() { if (rootWithPendingPassiveEffects === null) {//Become root after mutation return false; } const unmountEffects = pendingPassiveHookEffectsUnmount; pendingPassiveHookEffectsUnmount = [];//The callback function of useEffect for (let i = 0; i < unmountEffects.length; i += 2) { const effect = ((unmountEffects[i]: any): HookEffect); //... const destroy = effect.destroy; destroy(); } const mountEffects = pendingPassiveHookEffectsMount;//The destroy function of useEffect pendingPassiveHookEffectsMount = []; for (let i = 0; i < mountEffects.length; i += 2) { const effect = ((unmountEffects[i]: any): HookEffect); //... const create = effect.create; effect.destroy = create(); } }
componentDidUpdate or componentDidMount Will be at commit Synchronous execution of stages(This will be discussed later),and useEffect Will be at commit Phase asynchronous scheduling, so it is suitable for processing side effects such as data requests > Note, and in render stages fiber node will be marked Placement Like the label, useEffect or useLayoutEffect There are also corresponding effect Tag,Corresponds in the source code export const Passive = /* */ 0b0000000001000000000;
function commitBeforeMutationEffects() { while (nextEffect !== null) { const current = nextEffect.alternate; const effectTag = nextEffect.effectTag; // getSnapshotBeforeUpdate will be executed in the commitBeforeMutationEffectOnFiber function if ((effectTag & Snapshot) !== NoEffect) { commitBeforeMutationEffectOnFiber(current, nextEffect); } // scheduleCallback scheduling useEffect if ((effectTag & Passive) !== NoEffect) { if (!rootDoesHavePassiveEffects) { rootDoesHavePassiveEffects = true; scheduleCallback(NormalSchedulerPriority, () => { flushPassiveEffects(); return null; }); } } nextEffect = nextEffect.nextEffect;//traverse effectList } }
commitMutationEffects commitMutationEffects mainly does the following things
- Call commitDetachRef to unbind ref (hook will be explained in Chapter 11)
- Perform corresponding dom operations according to effectTag
- The useLayoutEffect destruction function is executed when UpdateTag
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) { //traverse effectList while (nextEffect !== null) { const effectTag = nextEffect.effectTag; // Call commitDetachRef to unbind ref if (effectTag & Ref) { const current = nextEffect.alternate; if (current !== null) { commitDetachRef(current); } } // Perform corresponding dom operations according to effectTag const primaryEffectTag = effectTag & (Placement | Update | Deletion | Hydrating); switch (primaryEffectTag) { // insert dom case Placement: { commitPlacement(nextEffect); nextEffect.effectTag &= ~Placement; break; } // insert update dom case PlacementAndUpdate: { // insert commitPlacement(nextEffect); nextEffect.effectTag &= ~Placement; // renew const current = nextEffect.alternate; commitWork(current, nextEffect); break; } //... // update dom case Update: { const current = nextEffect.alternate; commitWork(current, nextEffect); break; } // delete dom case Deletion: { commitDeletion(root, nextEffect, renderPriorityLevel); break; } } nextEffect = nextEffect.nextEffect; } }
Now let's look at the operation dom of these functions **commitPlacement Insert node:** The simplified code is very clear, find the nearest node parent node and sibling nodes, then according to isContainer To determine whether to insert before the sibling node or append arrive parent after node
function commitPlacement(finishedWork: Fiber): void { //... const parentFiber = getHostParentFiber(finishedWork);//find the nearest parent let parent; let isContainer; const parentStateNode = parentFiber.stateNode; switch (parentFiber.tag) { case HostComponent: parent = parentStateNode; isContainer = false; break; //... } const before = getHostSibling(finishedWork);//Find sibling nodes if (isContainer) { insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent); } else { insertOrAppendPlacementNode(finishedWork, before, parent); } }
**commitWork update node:** In the simplified source code you can see if fiber of tag yes SimpleMemoComponent will call commitHookEffectListUnmount Execute the corresponding hook The destruction function, you can see that the incoming parameters are HookLayout | HookHasEffect,That is to say, to execute useLayoutEffect the destruction function. if HostComponent,then call commitUpdate,commitUpdate will finally call updateDOMProperties Handling correspondence Update of dom operate
function commitWork(current: Fiber | null, finishedWork: Fiber): void { if (!supportsMutation) { switch (finishedWork.tag) { //... case SimpleMemoComponent: { commitHookEffectListUnmount(HookLayout | HookHasEffect, finishedWork); } //... } } switch (finishedWork.tag) { //... case HostComponent: { //... commitUpdate( instance, updatePayload, type, oldProps, newProps, finishedWork, ); } return; } }
function updateDOMProperties( domElement: Element, updatePayload: Array<any>, wasCustomComponentTag: boolean, isCustomComponentTag: boolean, ): void { // TODO: Handle wasCustomComponentTag for (let i = 0; i < updatePayload.length; i += 2) { const propKey = updatePayload[i]; const propValue = updatePayload[i + 1]; if (propKey === STYLE) { setValueForStyles(domElement, propValue); } else if (propKey === DANGEROUSLY_SET_INNER_HTML) { setInnerHTML(domElement, propValue); } else if (propKey === CHILDREN) { setTextContent(domElement, propValue); } else { setValueForProperty(domElement, propKey, propValue, isCustomComponentTag); } } }
**commitDeletion delete node:** if ClassComponent will execute componentWillUnmount,delete fiber,if FunctionComponent will delete ref,and execute useEffect The destruction function of , you can view it in the source code for details unmountHostComponents,commitNestedUnmounts,detachFiberMutation these functions
function commitDeletion( finishedRoot: FiberRoot, current: Fiber, renderPriorityLevel: ReactPriorityLevel, ): void { if (supportsMutation) { // Recursively delete all host nodes from the parent. // Detach refs and call componentWillUnmount() on the whole subtree. unmountHostComponents(finishedRoot, current, renderPriorityLevel); } else { // Detach refs and call componentWillUnmount() on the whole subtree. commitNestedUnmounts(finishedRoot, current, renderPriorityLevel); } const alternate = current.alternate; detachFiberMutation(current); if (alternate !== null) { detachFiberMutation(alternate); } }
commitLayoutEffects After commitMutationEffects, all dom operations have been completed, and dom can be accessed, and commitLayoutEffects mainly does
- Call commitLayoutEffectOnFiber to execute related life cycle functions or hook related callback s
- Execute commitAttachRef to assign value to ref
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) { while (nextEffect !== null) { const effectTag = nextEffect.effectTag; // Call commitLayoutEffectOnFiber to execute the life cycle and hook if (effectTag & (Update | Callback)) { const current = nextEffect.alternate; commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes); } // ref assignment if (effectTag & Ref) { commitAttachRef(nextEffect); } nextEffect = nextEffect.nextEffect; } }
**commitLayoutEffectOnFiber:** in the source code commitLayoutEffectOnFiber The alias of the function is commitLifeCycles,As you can see in the simplified code, commitLifeCycles will judge fiber type, SimpleMemoComponent will execute useLayoutEffect callback, and then dispatch useEffect,ClassComponent will execute componentDidMount or componentDidUpdate,this.setState The second parameter is also executed, HostRoot will execute ReactDOM.render The third parameter of the function, for example
ReactDOM.render(<App />, document.querySelector("#root"), function() { console.log("root mount"); });
can now know useLayoutEffect is in commit stages are executed synchronously, useEffect Will be at commit Phase Asynchronous Scheduling
function commitLifeCycles( finishedRoot: FiberRoot, current: Fiber | null, finishedWork: Fiber, committedLanes: Lanes, ): void { switch (finishedWork.tag) { case SimpleMemoComponent: { // This function will call the callback of useLayoutEffect commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork); // push effect to pendingPassiveHookEffectsUnmount and pendingPassiveHookEffectsMount // and schedule them schedulePassiveEffects(finishedWork); } case ClassComponent: { //conditional judgment... instance.componentDidMount(); //conditional judgment... instance.componentDidUpdate(//update is executed synchronously during layout prevProps, prevState, instance.__reactInternalSnapshotBeforeUpdate, ); } case HostRoot: { commitUpdateQueue(finishedWork, updateQueue, instance);//render the third parameter } } }
exist schedulePassiveEffects General will useEffect The destruction and callback functions of push arrive pendingPassiveHookEffectsUnmount with pendingPassiveHookEffectsMount middle
function schedulePassiveEffects(finishedWork: Fiber) { const updateQueue: FunctionComponentUpdateQueue | null = (finishedWork.updateQueue: any); const lastEffect = updateQueue !== null ? updateQueue.lastEffect : null; if (lastEffect !== null) { const firstEffect = lastEffect.next; let effect = firstEffect; do { const {next, tag} = effect; if ( (tag & HookPassive) !== NoHookEffect && (tag & HookHasEffect) !== NoHookEffect ) { //Push the destroy function of useEffect and add it to the schedule enqueuePendingPassiveHookEffectUnmount(finishedWork, effect); //Push the callback function of useEffect and add it to the schedule enqueuePendingPassiveHookEffectMount(finishedWork, effect); } effect = next; } while (effect !== firstEffect); } }
**commitAttachRef:** commitAttacRef will judge ref type of execution ref or give ref.current assignment
function commitAttachRef(finishedWork: Fiber) { const ref = finishedWork.ref; if (ref !== null) { const instance = finishedWork.stateNode; let instanceToUse; switch (finishedWork.tag) { case HostComponent: instanceToUse = getPublicInstance(instance); break; default: instanceToUse = instance; } if (typeof ref === "function") { // execute ref callback ref(instanceToUse); } else { // If it is a value type, assign it to ref.current ref.current = instanceToUse; } } }