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阶段前置工作

    1. 调用flushPassiveEffects执行完所有effect的工作
    2. 初始化相干变量
    3. 赋值firstEffect给前面遍历effectList用

      //ReactFiberWorkLoop.old.jsdo {    // 调用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 后

    1. 依据rootDoesHavePassiveEffects赋值相干变量
    2. 执行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
    该函数次要做了如下两件事

    1. 执行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

    在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; ```js 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   } }  ``` 
  • commitMutationEffects
    commitMutationEffects次要做了如下几件事

    1. 调用commitDetachRef解绑ref(第11章hook会解说)
    2. 依据effectTag执行对应的dom操作
    3. 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节点后 ```js 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操作  ```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删除节点:**     如果是ClassComponent会执行componentWillUnmount,删除fiber,如果是FunctionComponent 会删除ref、并执行useEffect的销毁函数,具体可在源码中查看unmountHostComponents、commitNestedUnmounts、detachFiberMutation这几个函数 ```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
    在commitMutationEffects之后所有的dom操作都曾经实现,能够拜访dom了,commitLayoutEffects次要做了

    1. 调用commitLayoutEffectOnFiber执行相干生命周期函数或者hook相干callback
    2. 执行commitAttachRef为ref赋值
  ```js 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函数的第三个参数,例如  ```js ReactDOM.render(<App />, document.querySelector("#root"), function() {   console.log("root mount"); }); ```  当初能够晓得useLayoutEffect是在commit阶段同步执行,useEffect会在commit阶段异步调度  ```js 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中  ```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 useEffect的销毁函数并且退出调度         enqueuePendingPassiveHookEffectUnmount(finishedWork, effect);         //push useEffect的回调函数并且退出调度         enqueuePendingPassiveHookEffectMount(finishedWork, effect);       }       effect = next;     } while (effect !== firstEffect);   } } ```  **commitAttachRef:**     commitAttacRef中会判断ref的类型,执行ref或者给ref.current赋值   ```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") {       // 执行ref回调       ref(instanceToUse);     } else {       // 如果是值的类型则赋值给ref.current       ref.current = instanceToUse;     }   } } ```