写点什么

react 源码解析 10.commit 阶段

作者:zz1998
  • 2021 年 12 月 14 日
  • 本文字数:8523 字

    阅读完需:约 28 分钟

react 源码解析 10.commit 阶段

视频讲解(高效学习):进入学习

往期文章:

1.开篇介绍和面试题


2.react的设计理念


3.react源码架构


4.源码目录结构和调试


5.jsx&核心api


6.legacy和concurrent模式入口函数


7.Fiber架构


8.render阶段


9.diff算法


10.commit阶段


11.生命周期


12.状态更新流程


13.hooks源码


14.手写hooks


15.scheduler&Lane


16.concurrent模式


17.context


18事件系统


19.手写迷你版react


20.总结&第一章的面试题解答


21.demo


在 render 阶段的末尾会调用 commitRoot(root);进入 commit 阶段,这里的 root 指的就是 fiberRoot,然后会遍历 render 阶段生成的 effectList,effectList 上的 Fiber 节点保存着对应的 props 变化。之后会遍历 effectList 进行对应的 dom 操作和生命周期、hooks 回调或销毁函数,各个函数做的事情如下


在 commitRoot 函数中其实是调度了 commitRootImpl 函数


//ReactFiberWorkLoop.old.jsfunction commitRoot(root) {  var renderPriorityLevel = getCurrentPriorityLevel();  runWithPriority$1(ImmediatePriority$1, commitRootImpl.bind(null, root, renderPriorityLevel));  return null;}
复制代码


在 commitRootImpl 的函数中主要分三个部分:


  • commit 阶段前置工作

  • 调用 flushPassiveEffects 执行完所有 effect 的任务

  • 初始化相关变量

  • 赋值 firstEffect 给后面遍历 effectList 用


     //ReactFiberWorkLoop.old.js     do {         // 调用flushPassiveEffects执行完所有effect的任务         flushPassiveEffects();       } while (rootWithPendingPassiveEffects !== null);            //...            // 重置变量 finishedWork指rooFiber       root.finishedWork = null;       //重置优先级       root.finishedLanes = NoLanes;            // Scheduler回调函数重置       root.callbackNode = null;       root.callbackId = NoLanes;            // 重置全局变量       if (root === workInProgressRoot) {         workInProgressRoot = null;         workInProgress = null;         workInProgressRootRenderLanes = NoLanes;       } else {       }             //rootFiber可能会有新的副作用 将它也加入到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 阶段

  • ​ 遍历 effectList 分别执行三个方法 commitBeforeMutationEffects、commitMutationEffects、commitLayoutEffects 执行对应的 dom 操作和生命周期

  • ​ 在介绍双缓存 Fiber 树的时候,我们在构建完 workInProgress Fiber 树之后会将 fiberRoot 的 current 指向 workInProgress Fiber,让 workInProgress Fiber 成为 current,这个步骤发生在 commitMutationEffects 函数执行之后,commitLayoutEffects 之前,因为 componentWillUnmount 发生在 commitMutationEffects 函数中,这时还可以获取之前的 Update,而 componentDidMountcomponentDidUpdate 会在 commitLayoutEffects 中执行,这时已经可以获取更新后的真实 dom 了


  function commitRootImpl(root, renderPriorityLevel) {    //...    do {        //...        commitBeforeMutationEffects();      } while (nextEffect !== null);        do {        //...        commitMutationEffects(root, renderPriorityLevel);//commitMutationEffects      } while (nextEffect !== null);        root.current = finishedWork;//切换current Fiber树        do {        //...        commitLayoutEffects(root, lanes);//commitLayoutEffects      } while (nextEffect !== null);    //...  }
复制代码


  • mutation 后

  • 根据 rootDoesHavePassiveEffects 赋值相关变量

  • 执行 flushSyncCallbackQueue 处理 componentDidMount 等生命周期或者 useLayoutEffect 等同步任务


     const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;          // 根据rootDoesHavePassiveEffects赋值相关变量     if (rootDoesHavePassiveEffects) {       rootDoesHavePassiveEffects = false;       rootWithPendingPassiveEffects = root;       pendingPassiveEffectsLanes = lanes;       pendingPassiveEffectsRenderPriority = renderPriorityLevel;     } else {}     //...          // 确保被调度     ensureRootIsScheduled(root, now());          // ...          // 执行flushSyncCallbackQueue处理componentDidMount等生命周期或者useLayoutEffect等同步任务     flushSyncCallbackQueue();          return null;
复制代码


现在让我们来看看 mutation 阶段的三个函数分别做了什么事情


  • commitBeforeMutationEffects 该函数主要做了如下两件事

  • 执行 getSnapshotBeforeUpdate 在源码中 commitBeforeMutationEffectOnFiber 对应的函数是 commitBeforeMutationLifeCycles 在该函数中会调用 getSnapshotBeforeUpdate,现在我们知道了 getSnapshotBeforeUpdate 是在 mutation 阶段中的 commitBeforeMutationEffect 函数中执行的,而 commit 阶段是同步的,所以 getSnapshotBeforeUpdate 也同步执行


     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. 调度 useEffect

  2. ​ 在 flushPassiveEffects 函数中调用 flushPassiveEffectsImpl 遍历 pendingPassiveHookEffectsUnmount 和 pendingPassiveHookEffectsMount,执行对应的 effect 回调和销毁函数,而这两个数组是在 commitLayoutEffects 函数中赋值的(待会就会讲到),mutation 后 effectList 赋值给 rootWithPendingPassiveEffects,然后 scheduleCallback 调度执行 flushPassiveEffects


     function flushPassiveEffectsImpl() {       if (rootWithPendingPassiveEffects === null) {//在mutation后变成了root         return false;       }       const unmountEffects = pendingPassiveHookEffectsUnmount;       pendingPassiveHookEffectsUnmount = [];//useEffect的回调函数       for (let i = 0; i < unmountEffects.length; i += 2) {         const effect = ((unmountEffects[i]: any): HookEffect);         //...         const destroy = effect.destroy;         destroy();       }            const mountEffects = pendingPassiveHookEffectsMount;//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或componentDidMount会在commit阶段同步执行(这个后面会讲到),而useEffect会在commit阶段异步调度,所以适用于数据请求等副作用的处理
> 注意,和在render阶段的fiber node会打上Placement等标签一样,useEffect或useLayoutEffect也有对应的effect Tag,在源码中对应export const Passive = /* */ 0b0000000001000000000;
复制代码


     function commitBeforeMutationEffects() {       while (nextEffect !== null) {         const current = nextEffect.alternate;         const effectTag = nextEffect.effectTag;              // 在commitBeforeMutationEffectOnFiber函数中会执行getSnapshotBeforeUpdate         if ((effectTag & Snapshot) !== NoEffect) {           commitBeforeMutationEffectOnFiber(current, nextEffect);         }              // scheduleCallback调度useEffect         if ((effectTag & Passive) !== NoEffect) {           if (!rootDoesHavePassiveEffects) {             rootDoesHavePassiveEffects = true;             scheduleCallback(NormalSchedulerPriority, () => {               flushPassiveEffects();               return null;             });           }         }         nextEffect = nextEffect.nextEffect;//遍历effectList       }     }
复制代码


  • commitMutationEffectscommitMutationEffects 主要做了如下几件事

  • 调用 commitDetachRef 解绑 ref(第 11 章 hook 会讲解)

  • 根据 effectTag 执行对应的 dom 操作

  • useLayoutEffect 销毁函数在 UpdateTag 时执行


     function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {       //遍历effectList       while (nextEffect !== null) {              const effectTag = nextEffect.effectTag;         // 调用commitDetachRef解绑ref         if (effectTag & Ref) {           const current = nextEffect.alternate;           if (current !== null) {             commitDetachRef(current);           }         }              // 根据effectTag执行对应的dom操作         const primaryEffectTag =           effectTag & (Placement | Update | Deletion | Hydrating);         switch (primaryEffectTag) {           // 插入dom           case Placement: {             commitPlacement(nextEffect);             nextEffect.effectTag &= ~Placement;             break;           }           // 插入更新dom           case PlacementAndUpdate: {             // 插入             commitPlacement(nextEffect);             nextEffect.effectTag &= ~Placement;             // 更新             const current = nextEffect.alternate;             commitWork(current, nextEffect);             break;           }            //...           // 更新dom           case Update: {             const current = nextEffect.alternate;             commitWork(current, nextEffect);             break;           }           // 删除dom           case Deletion: {             commitDeletion(root, nextEffect, renderPriorityLevel);             break;           }         }              nextEffect = nextEffect.nextEffect;       }     }
复制代码


 现在让我们来看看操作dom的这几个函数
**commitPlacement插入节点:**
​ 简化后的代码很清晰,找到该节点最近的parent节点和兄弟节点,然后根据isContainer来判断是插入到兄弟节点前还是append到parent节点后
复制代码


     unction commitPlacement(finishedWork: Fiber): void {       //...       const parentFiber = getHostParentFiber(finishedWork);//找到最近的parent            let parent;       let isContainer;       const parentStateNode = parentFiber.stateNode;       switch (parentFiber.tag) {         case HostComponent:           parent = parentStateNode;           isContainer = false;           break;         //...            }       const before = getHostSibling(finishedWork);//找兄弟节点       if (isContainer) {         insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);       } else {         insertOrAppendPlacementNode(finishedWork, before, parent);       }     }
复制代码


 **commitWork更新节点:**
​ 在简化后的源码中可以看到
​ 如果fiber的tag是SimpleMemoComponent会调用commitHookEffectListUnmount执行对应的hook的销毁函数,可以看到传入的参数是HookLayout | HookHasEffect,也就是说执行useLayoutEffect的销毁函数。
​ 如果是HostComponent,那么调用commitUpdate,commitUpdate最后会调用updateDOMProperties处理对应Update的dom操作
复制代码


     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删除节点:**   如果是ClassComponent会执行componentWillUnmount,删除fiber,如果是FunctionComponent 会删除ref、并执行useEffect的销毁函数,具体可在源码中查看unmountHostComponents、commitNestedUnmounts、detachFiberMutation这几个函数
复制代码


     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 在 commitMutationEffects 之后所有的 dom 操作都已经完成,可以访问 dom 了,commitLayoutEffects 主要做了

  • 调用 commitLayoutEffectOnFiber 执行相关生命周期函数或者 hook 相关 callback

  • 执行 commitAttachRef 为 ref 赋值


     function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {       while (nextEffect !== null) {         const effectTag = nextEffect.effectTag;              // 调用commitLayoutEffectOnFiber执行生命周期和hook         if (effectTag & (Update | Callback)) {           const current = nextEffect.alternate;           commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);         }              // ref赋值         if (effectTag & Ref) {           commitAttachRef(nextEffect);         }              nextEffect = nextEffect.nextEffect;       }     }
复制代码


 **commitLayoutEffectOnFiber:**
​ 在源码中commitLayoutEffectOnFiber函数的别名是commitLifeCycles,在简化后的代码中可以看到,commitLifeCycles会判断fiber的类型,SimpleMemoComponent会执行useLayoutEffect的回调,然后调度useEffect,ClassComponent会执行componentDidMount或者componentDidUpdate,this.setState第二个参数也会执行,HostRoot会执行ReactDOM.render函数的第三个参数,例如
复制代码


     ReactDOM.render(<App />, document.querySelector("#root"), function() {       console.log("root mount");     });
复制代码


 现在可以知道useLayoutEffect是在commit阶段同步执行,useEffect会在commit阶段异步调度
复制代码


     function commitLifeCycles(       finishedRoot: FiberRoot,       current: Fiber | null,       finishedWork: Fiber,       committedLanes: Lanes,     ): void {       switch (finishedWork.tag) {         case SimpleMemoComponent: {           // 此函数会调用useLayoutEffect的回调           commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);           // 向pendingPassiveHookEffectsUnmount和pendingPassiveHookEffectsMount中push effect            // 并且调度它们           schedulePassiveEffects(finishedWork);         }         case ClassComponent: {           //条件判断...           instance.componentDidMount();           //条件判断...           instance.componentDidUpdate(//update 在layout期间同步执行             prevProps,             prevState,             instance.__reactInternalSnapshotBeforeUpdate,           );               }                        case HostRoot: {           commitUpdateQueue(finishedWork, updateQueue, instance);//render第三个参数         }                }     }
复制代码


 ​  在schedulePassiveEffects中会将useEffect的销毁和回调函数push到pendingPassiveHookEffectsUnmount和pendingPassiveHookEffectsMount中
复制代码


     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 useEffect的销毁函数并且加入调度             enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);             //push useEffect的回调函数并且加入调度             enqueuePendingPassiveHookEffectMount(finishedWork, effect);           }           effect = next;         } while (effect !== firstEffect);       }     }
复制代码


 **commitAttachRef:**
​ commitAttacRef中会判断ref的类型,执行ref或者给ref.current赋值
复制代码


     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") {           // 执行ref回调           ref(instanceToUse);         } else {           // 如果是值的类型则赋值给ref.current           ref.current = instanceToUse;         }       }     }
复制代码


用户头像

zz1998

关注

还未添加个人签名 2021.06.26 加入

还未添加个人简介

评论

发布
暂无评论
react源码解析10.commit阶段