react源码系列 -- commit阶段

331 阅读3分钟

commit阶段

commit分三个阶段: before mutation阶段,mutation阶段,layout阶段

来分别看下三个阶段做了什么事情:

Before Mutation

该阶段会执行 commitBeforeMutationEffects 函数,整体做了2件事情,

  1. DOM节点删除以后的autoFous, blur的逻辑

  2. 调用生命周期getSnapshotBeforeUpdate生命周期钩子

  3. 异步调用useEffect (v18 没看到调用)

在React16以后,componetWillxxx钩子前面加了UNSAFE_前缀,原因是:这些钩子在render阶段,而Stack Reconciler 重构为Fiber Reconciler以后,render阶段是可以被中断的,在其的生命周期钩子可能会被执行多次。

所以react 提供了代替的生命周期钩子,getSnapshotBeforeUpdate。因为其在commit阶段,并commit 不会被中断,所以只会执行一次。

commitBeforeMutationEffects() {
    ...
}
...
instance.getSnapshotBeforeUpdate()
...

问题: 每一个setState 都会触发 从根节点到叶子节点的render,commiit 2个过程吗

问题: useEffect是如何执行的,为什么没有注入依赖就拿不到改数据?

Mutation

Mutation主要执行commitMutationEffects函数:

function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
  // 遍历effectList
  while (nextEffect !== null) {

    const effectTag = nextEffect.effectTag;

    // 根据 ContentReset effectTag重置文字节点
    if (effectTag & ContentReset) {
      commitResetTextContent(nextEffect);
    }

    // 更新ref
    if (effectTag & Ref) {
      const current = nextEffect.alternate;
      if (current !== null) {
        commitDetachRef(current);
      }
    }

    // 根据 effectTag 分别处理
    const primaryEffectTag =
      effectTag & (Placement | Update | Deletion | Hydrating);
    switch (primaryEffectTag) {
      // 插入DOM
      case Placement: {
        commitPlacement(nextEffect);
        nextEffect.effectTag &= ~Placement;
        break;
      }
      // 插入DOM 并 更新DOM
      case PlacementAndUpdate: {
        // 插入
        commitPlacement(nextEffect);

        nextEffect.effectTag &= ~Placement;

        // 更新
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      // SSR
      case Hydrating: {
        nextEffect.effectTag &= ~Hydrating;
        break;
      }
      // SSR
      case HydratingAndUpdate: {
        nextEffect.effectTag &= ~Hydrating;

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

可以看到主要是一个while循环,循环包括effectTag的effectList。

首先判断ContentReset,表示是否要重置文本节点

接下来是判断是否有ref 的更新

再接下来就是该阶段最重要的dom操作。

删除dom阶段会执行componentwillUnmount钩子

layout

layout阶段也是遍历effectList,总共做了如下几件事情;

    1. 改变root.current指向。         把内存中的fiber树变为当前屏幕渲染的节点树。原因:componetWillUnmount是在该阶段前,访问的还是以前的dom实例,而componetDidMount是在该改变之后,在该生命周期访问的是新的dom实例。

    2. 执行 commitLayoutEffectOnFiber方法。

        做了如下操作:执行遍历所有的useLayoutEffects,并且执行其回调函数。         值得注意的是在Mutation阶段会先执行所有useLayoutEffects的销毁函数,然后在Layout阶段会执行所有的回调函数,且这个所有过程是同步的。

    3. 判断是否有Passive,即是否存在useEffect的回调函数。如果存在,则会异步调用flushPassiveEffects方法。flushPassiveEffects:遍历执行useEffect的回调函数。     注意⚠️:由于我们的useEffect的回调函数可能产生新的更新,所以回去循环执行flshPassiveEffects直到rootWithPendingPassiveEffects为null。

总结useEffect和useLayoutEffect的区别:useLayoutEffect会在Mutation阶段执行其销毁函数,在Layout阶段执行其回调函数,整个过程为同步过程。useEffect会异步调用flushPassiveEffects,在Layout阶段注册销毁和回调函数,在commit阶段完成后执行,整个过程为异步。