react source code analysis 10 Commit phase

react source code analysis 10 Commit phase

Video Explanation (efficient learning): Enter learning

Previous articles:

1. Introduction and interview questions

2. Design concept of react

3.react source code architecture

4. Source directory structure and debugging

5. JSX & Core api

6.legacy and concurrent mode entry functions

7.Fiber architecture

8.render stage

9.diff algorithm

10.commit phase

11. Life cycle

12. Status update process

13.hooks source code

14. Handwritten hooks

15.scheduler&Lane

16.concurrent mode

17.context

18 event system

19. Handwritten Mini react

20. Summary & answers to interview questions in Chapter 1

21.demo

commitRoot(root) will be called at the end of the render phase; Enter the commit phase, where root refers to fiberRoot, and then traverse the effectList generated in the render phase. The Fiber node on the effectList saves the corresponding props changes. After that, it will traverse the effectList to perform the corresponding dom operations and life cycle, hooks callback or destroy functions. What each function does is as follows

The commitRootImpl function is actually scheduled in the commitRoot function

//ReactFiberWorkLoop.old.js
function commitRoot(root) {
  var renderPriorityLevel = getCurrentPriorityLevel();
  runWithPriority$1(ImmediatePriority$1, commitRootImpl.bind(null, root, renderPriorityLevel));
  return null;
}

The commitRootImpl function is mainly divided into three parts:

  • Pre work in commit phase

    1. Call flushpassive effects to complete the task of all effects
    2. Initialize related variables
    3. Assign firstEffect to the following traversal of the effectList

      //ReactFiberWorkLoop.old.js
      do {
          // Call flushpassive effects to complete the task of all effects
          flushPassiveEffects();
        } while (rootWithPendingPassiveEffects !== null);
      
          //...
      
        // The reset variable finishedWork refers to rooFiber
        root.finishedWork = null;
          //Reset priority
        root.finishedLanes = NoLanes;
      
        // Scheduler callback function reset
        root.callbackNode = null;
        root.callbackId = NoLanes;
      
        // Reset global variables
        if (root === workInProgressRoot) {
          workInProgressRoot = null;
          workInProgress = null;
          workInProgressRootRenderLanes = NoLanes;
        } else {
        }
      
           //rootFiber may have new side effects and add it to effectLis
        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

    Traverse the effectList and execute three methods, commitBeforeMutationEffects, commitMutationEffects and commitLayoutEffects, respectively, to execute the corresponding dom operation and life cycle

    When introducing the dual cache Fiber tree, after building the workInProgress Fiber tree, we will point the current of the fiberRoot to the workInProgress Fiber to make the workInProgress Fiber current. This step occurs after the commitMutationEffects function is executed and before commitLayoutEffects, because componentWillUnmount occurs in the commitMutationEffects function, At this time, you can also get the previous Update, and componentDidMount and componentDidUpdate will be executed in commitLayoutEffects. At this time, you can get the updated real dom

    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

    1. Assign relevant variables according to rootdoeshavepassive effects
    2. Execute flushSyncCallbackQueue to handle life cycles such as componentDidMount or synchronization tasks such as uselayouteeffect

      const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
      
      // Assign relevant variables according to rootdoeshavepassive effects
      if (rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = false;
        rootWithPendingPassiveEffects = root;
        pendingPassiveEffectsLanes = lanes;
        pendingPassiveEffectsRenderPriority = renderPriorityLevel;
      } else {}
      //...
      
      // Ensure scheduled
      ensureRootIsScheduled(root, now());
      
      // ...
      
      // Execute flushSyncCallbackQueue to handle life cycles such as componentDidMount or synchronization tasks such as uselayouteeffect
      flushSyncCallbackQueue();
      
      return null;

Now let's look at what the three functions in the mutation phase do respectively

  • commitBeforeMutationEffects
    This function mainly does the following two things

    1. Execute getSnapshotBeforeUpdate

      In the source code commitBeforeMutationEffectOnFiber The corresponding function is commitBeforeMutationLifeCycles Called in this function getSnapshotBeforeUpdate,Now we know getSnapshotBeforeUpdate Yes mutation In phase commitBeforeMutationEffect Function, and commit The phases are synchronized, so getSnapshotBeforeUpdate Also execute 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,
                );
              }
      }
  1. Scheduling useEffect

    In the flushPassiveEffects function, flushPassiveEffectsImpl is called to traverse pendingPassiveHookEffectsUnmount and pendingPassiveHookEffectsMount, and the corresponding effect callbacks and destruct functions are executed. The two arrays are assigned in the commitLayoutEffects function (which will be discussed later). After the mutation, the effectList is assigned to rootwithpendingpassive effects, and then the scheduleCallback schedules the execution of flushpassive effects

    function flushPassiveEffectsImpl() {
      if (rootWithPendingPassiveEffects === null) {//After mutation, it becomes root
        return false;
      }
      const unmountEffects = pendingPassiveHookEffectsUnmount;
      pendingPassiveHookEffectsUnmount = [];//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;//Destruction 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 commit Phase synchronous execution(This will be discussed later),and useEffect Will be commit Stage asynchronous scheduling, so it is suitable for the processing of side effects such as data requests

 > Attention, and in render Phasic fiber node I'll call Placement Like the label, useEffect or useLayoutEffect There are also corresponding effect Tag,Corresponding in the source code export const Passive = /*                      */ 0b0000000001000000000;

 ```js
 function commitBeforeMutationEffects() {
   while (nextEffect !== null) {
     const current = nextEffect.alternate;
     const effectTag = nextEffect.effectTag;
 
     // getSnapshotBeforeUpdate is executed in the commitBeforeMutationEffectOnFiber function
     if ((effectTag & Snapshot) !== NoEffect) {
       commitBeforeMutationEffectOnFiber(current, nextEffect);
     }
 
     // scheduleCallback scheduleuseeffect
     if ((effectTag & Passive) !== NoEffect) {
       if (!rootDoesHavePassiveEffects) {
         rootDoesHavePassiveEffects = true;
         scheduleCallback(NormalSchedulerPriority, () => {
           flushPassiveEffects();
           return null;
         });
       }
     }
     nextEffect = nextEffect.nextEffect;//Traverse the effectList
   }
 }
 
 ```

 
  • commitMutationEffects
    commitMutationEffects mainly does the following things

    1. Call commitDetachRef to unbind ref (explained in hook in Chapter 11)
    2. Perform the corresponding dom operation according to the effectTag
    3. The uselayouteeffect destroy function is executed at UpdateTag

      function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
        //Traverse the 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 the corresponding dom operation according to the 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;
              // to update
              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 These functions

 **commitPlacement Insert node:**

 ​    The simplified code is very clear. Find the nearest node parent Node and sibling node, and then according to isContainer To determine whether it is inserted before or after the sibling node append reach parent After node

 ```js
 unction 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 node
   if (isContainer) {
     insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
   } else {
     insertOrAppendPlacementNode(finishedWork, before, parent);
   }
 }
 ```

 

 **commitWork Update node:**

 ​    You can see in the simplified source code

 ​        If fiber of tag yes SimpleMemoComponent Will call commitHookEffectListUnmount Execute the corresponding hook You can see that the parameters passed in are HookLayout | HookHasEffect,That is, execution useLayoutEffect Destroy function for.

 ​        If it is HostComponent,Then call commitUpdate,commitUpdate Finally, it will call updateDOMProperties Processing correspondence Update of dom operation

 

 ```js
 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;
     }
 }
 ```

 

 

 ```js
 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 it is ClassComponent Will execute componentWillUnmount,delete fiber,If it is FunctionComponent Will delete ref,And execute useEffect The specific destruction function can be viewed in the source code unmountHostComponents,commitNestedUnmounts,detachFiberMutation These functions

 ```js
 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 the dom can be accessed. commitLayoutEffects mainly does

    1. Call commitLayoutEffectOnFiber to execute related life cycle functions or hook related callback
    2. Execute committatchref to assign a value to ref
 

 ```js
 function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
   while (nextEffect !== null) {
     const effectTag = nextEffect.effectTag;
 
     // Call commitLayoutEffectOnFiber to execute lifecycle 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 for the function is commitLifeCycles,As you can see in the simplified code, commitLifeCycles Can judge fiber Type of, SimpleMemoComponent Will execute useLayoutEffect Callbacks, then scheduling useEffect,ClassComponent Will execute componentDidMount perhaps componentDidUpdate,this.setState The second parameter will also be executed, HostRoot Will execute ReactDOM.render The third parameter of the function, for example

 

 ```js
 ReactDOM.render(<App />, document.querySelector("#root"), function() {
   console.log("root mount");
 });
 ```

 

 Now you know useLayoutEffect Yes commit Phase synchronization, useEffect Will be commit Phase asynchronous scheduling

 

 ```js
 function commitLifeCycles(
   finishedRoot: FiberRoot,
   current: Fiber | null,
   finishedWork: Fiber,
   committedLanes: Lanes,
 ): void {
   switch (finishedWork.tag) {
     case SimpleMemoComponent: {
       // This function calls the callback of useLayoutEffect
       commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);
       // push effect / / into 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 third parameter
     }
     
   }
 }
 ```

 

 ​    stay schedulePassiveEffects Zhonghuijiang useEffect Destruction and callback functions push reach pendingPassiveHookEffectsUnmount and pendingPassiveHookEffectsMount in

 

 ```js
 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 destruction function of useeffect and add scheduling
         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

 

 

 ```js
 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 the type of value, it is assigned to ref.current
       ref.current = instanceToUse;
     }
   }
 }
 ```

 

 


Keywords: React

Added by mooler on Tue, 14 Dec 2021 03:47:00 +0200