Skip to content

commitWork 阶段

beginWorkcompleteWork阶段都正常结束后,此时所有的fiber和真实节点创建完成,进入到commit阶段:

javascript
const finishedWork: Fiber = (root.current.alternate: any);
root.finishedWork = finishedWork;
root.finishedLanes = lanes;
finishConcurrentRender(root, exitStatus, lanes);

当结果为RootCompleted时,finishConcurrentRender函数会调用commitRoot(root)方法:

javascript
function commitRoot(root) {
  const previousUpdateLanePriority = getCurrentUpdatePriority();
  const prevTransition = ReactCurrentBatchConfig.transition;
  try {
    ReactCurrentBatchConfig.transition = 0;
    // 优先级为同步,不可被打断
    setCurrentUpdatePriority(DiscreteEventPriority);
    commitRootImpl(root, previousUpdateLanePriority);
  } finally {
    ReactCurrentBatchConfig.transition = prevTransition;
    setCurrentUpdatePriority(previousUpdateLanePriority);
  }

  return null;
}

commit执行时,会将当前优先级置为DiscreteEventPriority,也就是同步优先级,为同步执行,不可被打断。

flushPassiveEffects

commit第一步会执行flushPassiveEffects方法:

javascript
do {
  flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);

那么什么时候rootWithPendingPassiveEffects !== null成立呢?在commitRootImpl方法后半段会进行赋值:

javascript
if (rootDoesHavePassiveEffects) {
  rootDoesHavePassiveEffects = false;
  rootWithPendingPassiveEffects = root;
  pendingPassiveEffectsLanes = lanes;
}

如果rootDoesHavePassiveEffects成立,那么会将root赋值给rootWithPendingPassiveEffects。那么下一轮commit时,rootWithPendingPassiveEffects !== null 就会成立了。还是在这个函数中,再看看rootDoesHavePassiveEffects变量:

javascript
if (
  (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
  (finishedWork.flags & PassiveMask) !== NoFlags
) {
  if (!rootDoesHavePassiveEffects) {
    rootDoesHavePassiveEffects = true;
    scheduleCallback(NormalSchedulerPriority, () => {
      flushPassiveEffects();
      return null;
    });
  }
}

如果flags/subtreeFlags中存在PassiveMask,即Passive|ChildDeletion,那么rootDoesHavePassiveEffectstrue。也就是说如果使用了useEffect或者是节点有删除的情况,那么就会执行flushPassiveEffects方法:

javascript
export function flushPassiveEffects(): boolean {
  // 如果 rootWithPendingPassiveEffects 存在,说明使用了 useEffect 或者有子节点被删除
  if (rootWithPendingPassiveEffects !== null) {
    const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
    const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
    const prevTransition = ReactCurrentBatchConfig.transition;
    const previousPriority = getCurrentUpdatePriority();
    try {
      // transition 置为 0
      ReactCurrentBatchConfig.transition = 0;
      // 设置 update 优先级,获取 lane 的时候会用得到
      setCurrentUpdatePriority(priority);
      return flushPassiveEffectsImpl();
    } finally {
      setCurrentUpdatePriority(previousPriority);
      ReactCurrentBatchConfig.transition = prevTransition;
    }
  }
  return false;
}

设置更新的优先级后,会执行两个方法:一个是卸载方法,一个是挂载方法。

javascript
// 从下到 sibling 到 return 这种深度遍历的方式,一次执行 effect 的 destroy 方法
commitPassiveUnmountEffects(root.current);
// 遍历 root.current 的 updateQueue,执行上面的 effect 的 create 方法
commitPassiveMountEffects(root, root.current);

commitPassiveUnmountEffects

commitPassiveUnmountEffects_begin

commitPassiveUnmountEffects实际调用的是commitPassiveUnmountEffects_begin方法,该方法首先会向下遍历,对于有ChildDeletion标记的都会遍历fiber.deletions(记录了需要删除的老fiber)执行真实节点的删除:

javascript
if ((nextEffect.flags & ChildDeletion) !== NoFlags) {
  const deletions = fiber.deletions;
  if (deletions !== null) {
    for (let i = 0;i < deletions.length;i++) {
      const fiberToDelete = deletions[i];
      nextEffect = fiberToDelete;
      // 进行具体 delete
      commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
        fiberToDelete,
        fiber,
      );
    }
  }
}

commitPassiveUnmountEffectsInsideOfDeletedTree_begin

commitPassiveUnmountEffectsInsideOfDeletedTree_begin方法同样会向下遍历,对于遍历到的function组件,执行卸载方法:

javascript
commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor);

function commitPassiveUnmountInsideDeletedTreeOnFiber(
  current: Fiber,
  nearestMountedAncestor: Fiber | null,
): void {
  switch (current.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: {
   // 执行卸载方法
      commitHookEffectListUnmount(
        HookPassive,
        current,
        nearestMountedAncestor,
      );
      break;
    }
  }
}

commitHookEffectListUnmount

commitHookEffectListUnmount方法会循环执行fiber上的effects(如果使用了useEffect/useLayoutEffect等,会创建effect,并形成链表挂载到fiber.updateQueue.effects.lastEffect上):

javascript
const firstEffect = lastEffect.next;
// 找到第一个 effect
let effect = firstEffect;
do {
  // 如果 tag 包含 flags
  if ((effect.tag & flags) === flags) {
    const destroy = effect.destroy;
    effect.destroy = undefined;
    if (destroy !== undefined) {
      safelyCallDestroy(finishedWork, nearestMountedAncestor, destroy);
    }
  }
  effect = effect.next;
} while (effect !== firstEffect);

由于这里的tagHookPassive,因此只有useEffect里的destroy方法才会被执行,而useLayoutEffect里的destroy不会被执行。

commitPassiveUnmountEffectsInsideOfDeletedTree_complete

在执行了destroy方法后,会删除fiber引用并且删除对应真实节点的引用:

javascript
function detachFiberAfterEffects(fiber: Fiber) {
  fiber.child = null;
  fiber.deletions = null;
  fiber.sibling = null;
  
  // ...

  if (fiber.tag === HostComponent) {
    const hostInstance: Instance = fiber.stateNode;
    if (hostInstance !== null) {
      detachDeletedInstance(hostInstance);
    }
  }
  fiber.stateNode = null;

 // ...
}

如果是普通便签,且fiber.stateNode存在,那么需要调用detachDeletedInstance方法删除node节点上的一些引用:

javascript
export function detachDeletedInstance(node: Instance): void {
  delete (node: any)[internalInstanceKey];
  delete (node: any)[internalPropsKey];
  delete (node: any)[internalEventHandlersKey];
  delete (node: any)[internalEventHandlerListenersKey];
  delete (node: any)[internalEventHandlesSetKey];
}

回到commitPassiveUnmountEffects_begin方法,在执行了老fiber子节点的删除后,还需要将老fiber与子节点,未删除子节点与已删除子节点之间的引用删除:

javascript
const previousFiber = fiber.alternate;
if (previousFiber !== null) {
  let detachedChild = previousFiber.child;
  if (detachedChild !== null) {
    previousFiber.child = null;
    do {
      const detachedSibling = detachedChild.sibling;
      // 挨个将 sibling 指向置空
      detachedChild.sibling = null;
      detachedChild = detachedSibling;
    } while (detachedChild !== null);
  }
}

commitPassiveUnmountEffects_complete

向下查找直到找到不是PassiveMask的节点,此时执行commitPassiveUnmountEffects_complete方法:

javascript
if ((fiber.flags & Passive) !== NoFlags) {
  // 循环执行 unmount 下的 destroy 方法
  commitPassiveUnmountOnFiber(fiber);
}

该方法会向上查找,是否有flags包含Passivefiber。如果有,同样会调用commitPassiveUnmountOnFiber执行effectdestroy方法。这样整棵树中有ChildDeletionPassive标记的fiber中的卸载方法就全都执行了。

commitPassiveMountEffects

commitPassiveMountEffects_begin

commitPassiveMountEffects_begin方法作用是向下查找,找到一个非PassiveMask标记的节点:

javascript
if ((fiber.subtreeFlags & PassiveMask) !== NoFlags && firstChild !== null) {
  ensureCorrectReturnPointer(firstChild, fiber);
  nextEffect = firstChild;
}

commitPassiveMountEffects_complete

commitPassiveMountEffects_complete方法会从下往上查找有Passive标记的节点,并执行其commitPassiveMountOnFiber方法:

javascript
if ((fiber.flags & Passive) !== NoFlags) {
  commitPassiveMountOnFiber(root, fiber);
}

commitPassiveMountOnFiber

对于函数式组件都会执行commitHookEffectListMount方法,此时hooktagHookPassive | HookHasEffect

javascript
function commitPassiveMountOnFiber(
  finishedRoot: FiberRoot,
   finishedWork: Fiber,
): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent: {
      commitHookEffectListMount(HookPassive | HookHasEffect, finishedWork);
      break;
    }
  }
}

commitHookEffectListMount

commitHookEffectListMount则是循环调用effects里的create方法并生成destroy

javascript
const firstEffect = lastEffect.next;
// 找到第一个 effect
let effect = firstEffect;
do {
  if ((effect.tag & tag) === tag) {
    // 调用 create 方法
    const create = effect.create;
    effect.destroy = create();
    effect = effect.next;
  }
} while (effect !== firstEffect);

变量重置

一些列全局的变量重置:

javascript
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;

root.finishedWork = null;
root.finishedLanes = NoLanes;

// 任务重置
root.callbackNode = null;
root.callbackPriority = NoLane;

// 移除 lane
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
markRootFinished(root, remainingLanes);

if (root === workInProgressRoot) {
 // 重置
  workInProgressRoot = null;
  workInProgress = null;
  workInProgressRootRenderLanes = NoLanes;
}

重置完成后判断是否具有PassiveMask(最开始的flushPassiveEffects与这里的判断有关)。如果之前rootDoesHavePassiveEffectsfalse,表示effect还未被记录,此时需要调用一次flushPassiveEffects方法。

javascript
if (
  (finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
  (finishedWork.flags & PassiveMask) !== NoFlags
) {
  if (!rootDoesHavePassiveEffects) {
    rootDoesHavePassiveEffects = true;
    scheduleCallback(NormalSchedulerPriority, () => {
      flushPassiveEffects();
      return null;
    });
  }
}

执行flushPassiveEffects时,会先遍历执行destroy,由于第一次创建没有destroy,所以不会执行。然后会遍历执行create,并且生成destroy

beforeMutation 阶段

commitBeforeMutationEffects

javascript
const subtreeHasEffects =
  (finishedWork.subtreeFlags &
    (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
  NoFlags;
const rootHasEffect =
  (finishedWork.flags &
    (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !==
  NoFlags;

如果subtreeHasEffectsrootHasEffect存在,说明有更新。首先会进入beforeMutation阶段,调用commitBeforeMutationEffects方法:

javascript
export function commitBeforeMutationEffects(
  root: FiberRoot,
  firstChild: Fiber,
) {
  // ...
  commitBeforeMutationEffects_begin();
  // ...
}

其核心是调用commitBeforeMutationEffects_begin方法。

commitBeforeMutationEffects_begin

flushPassiveEffects中类似,这里以_begin结尾的函数都是向下遍历,直到找到一个不符合BeforeMutationMask的节点。

javascript
export const BeforeMutationMask = Update | Snapshot

一种是标记了Update的节点,一种是标记SnapShot的节点,如在class组件中定义了getSnapshotBeforeUpdate函数,那么在beginWork阶段就会将fiber标记为SnapShot

commitBeforeMutationEffects_complete

_complete结尾的函数都是从下往上遍历,然后执行某个函数。这里执行的函数为commitBeforeMutationEffectsOnFiber

commitBeforeMutationEffectsOnFiber

javascript
if ((flags & Snapshot) !== NoFlags) {
  switch (finishedWork.tag) {
    case ClassComponent: {
      if (current !== null) {
        const prevProps = current.memoizedProps;
        const prevState = current.memoizedState;
        const instance = finishedWork.stateNode;
        const snapshot = instance.getSnapshotBeforeUpdate(
          finishedWork.elementType === finishedWork.type
          ? prevProps
          : resolveDefaultProps(finishedWork.type, prevProps),
          prevState,
        );
        instance.__reactInternalSnapshotBeforeUpdate = snapshot;
      }
      break;
    }
    case HostRoot: {
      if (supportsMutation) {
        const root = finishedWork.stateNode;
        clearContainer(root.containerInfo);
      }
      break;
    }
  }
}

如果SnapShot标记主要有两种情况:一种是HostRoot,在completeWork阶段会将其标记:

javascript
if (current === null || current.child === null) {
  workInProgress.flags |= Snapshot;
}

如果current或current.child不存在,在commit阶段会将root.containerInfo真实节点的内容置空。

另一种情况是class组件定义了getSnapshotBeforeUpdate,此时会直接调用该函数,传入的值为先前的propsstate

mutation 阶段

commitMutationEffects

commitMutationEffects执行的是commitMutationEffects_begin方法:

javascript
const deletions = fiber.deletions;
if (deletions !== null) {
  for (let i = 0;i < deletions.length;i++) {
    const childToDelete = deletions[i];
    commitDeletion(root, childToDelete, fiber);
  }
}

首先是循环遍历,向下执commitDeletion方法,等同于执行了unmountHostComponents方法。

unmountHostComponents

unmountHostComponents方法作用是删除fiber对应的真实节点。首先是找到这个fiber的父真实节点:

javascript
if (!currentParentIsValid) {
  let parent = node.return;
  findParent: while (true) {
    const parentStateNode = parent.stateNode;
    switch (parent.tag) {
      case HostComponent:
        currentParent = parentStateNode;
        currentParentIsContainer = false;
        break findParent;
      case HostRoot:
        currentParent = parentStateNode.containerInfo;
        currentParentIsContainer = true;
        break findParent;
      case HostPortal:
        currentParent = parentStateNode.containerInfo;
        currentParentIsContainer = true;
        break findParent;
    }
    parent = parent.return;
  }
  currentParentIsValid = true;
}

只有HostComponent、HostRoot、HostPortal才会渲染成真实节点,其他的标签都是React内部定义,不会渲染出来。

然后判断当前节点的类型,如果是HostComponentHostText类型:

javascript
if (node.tag === HostComponent || node.tag === HostText) {
  // 1. 递归触发卸载事件。(useLayoutEffect/componentWillUnmount)
  commitNestedUnmounts(finishedRoot, node, nearestMountedAncestor);
  // 2. 触发完事件,就需要移除真实节点
  if (currentParentIsContainer) {
    removeChildFromContainer(
      ((currentParent: any): Container),
      (node.stateNode: Instance | TextInstance),
    );
  } else {
    // 移除真实节点
    removeChild(
      ((currentParent: any): Instance),
      (node.stateNode: Instance | TextInstance),
    );
  }
}

首先是调用commitNestedUnmounts方法,这个方法会遍历该节点下的所有fiber,然后调用commitUnmount方法。(注意:向下遍历遇到HostPortal时不会调用commitUnmount)。

commitUnmount

对于函数式组件,会循环执行带有Layouttageffect

javascript
do {
  const { destroy, tag } = effect;
  if (destroy !== undefined) {
    // 执行 layout 的 destroy,也就是 useLayoutEffect
    if ((tag & HookLayout) !== NoHookEffect) {
      safelyCallDestroy(current, nearestMountedAncestor, destroy);
    }
  }
  effect = effect.next;
} while (effect !== firstEffect);

对于class组件,一是移除ref,二是调用componentWillUnmount方法:

javascript
safelyDetachRef(current, nearestMountedAncestor);
const instance = current.stateNode;
if (typeof instance.componentWillUnmount === 'function') {
  safelyCallComponentWillUnmount(
    current,
    nearestMountedAncestor,
    instance,
  );
}

对于HostPortal,则是调用unmountHostComponents处理它的子节点。

commitMutationEffects_complete

等删除节点操作完成后,回到commitMutationEffects_begin方法,会开始执行commitMutationEffects_complete方法。该方法会向上查找,并且调用commitMutationEffectsOnFiber方法。

commitMutationEffectsOnFiber

对于不同的flagsmutation阶段会做不同的处理:

ContentReset

表示需要按文本内容处理,并将内容置空:

javascript
commitResetTextContent(finishedWork);

Visibility

suspense相关处理,suspense章节再做讲解。

Placement

表示插入节点。第一步需要找到该节点的父fiber节,注意,这里的父fiber必须是能够渲染成真实DOMfiber

javascript
const parentFiber = getHostParentFiber(finishedWork);

function getHostParentFiber(fiber: Fiber): Fiber {
  let parent = fiber.return;
  while (parent !== null) {
    if (isHostParent(parent)) {
      return parent;
    }
    parent = parent.return;
  }
}

function isHostParent(fiber: Fiber): boolean {
  return (
    fiber.tag === HostComponent ||
    fiber.tag === HostRoot ||
    fiber.tag === HostPortal
  );
}

第二步通过父fiber获取真实节点:

javascript
const parentStateNode = parentFiber.stateNode;
// 找到真实父节点
switch (parentFiber.tag) {
  case HostComponent:
    parent = parentStateNode;
    isContainer = false;
    break;
  case HostRoot:
    // 注意这里是添加到根节点上去了。
    parent = parentStateNode.containerInfo;
    isContainer = true;
    break;
  case HostPortal:
    // 注意这里是添加到根节点上去了。
    parent = parentStateNode.containerInfo;
    isContainer = true;
    break;
}

第三步获取兄弟结点:

javascript
const before = getHostSibling(finishedWork);

function getHostSibling(fiber: Fiber): ?Instance {
  siblings: while (true) {
    // 如果 sibling 不存在
    while (node.sibling === null) {
      // 如果 return 不存在 获取 父亲是可以渲染成真实节点的 fiber
      // 那么说明 sibling 确实是不存在的
      if (node.return === null || isHostParent(node.return)) {
        return null;
      }
      node = node.return;
    }
    // sibling 存在时
    node.sibling.return = node.return;
    node = node.sibling;
    // 向下查找,查找合适的主节点
    while (
      node.tag !== HostComponent &&
      node.tag !== HostText &&
      node.tag !== DehydratedFragment
    ) {
      // 如果是标记为Placement的,那么是不可用的。
      if (node.flags & Placement) {
        continue siblings;
      }
      if (node.child === null || node.tag === HostPortal) {
        continue siblings;
      } else {
        // 查找孩子节点(因为react中fiber与实际的真实并不是完全对应的,如有的会形成一层包装)
        node.child.return = node;
        node = node.child;
      }
    }

    // 如果找到的节点是不需要插入的节点,则返回该节点。
    if (!(node.flags & Placement)) {
      // Found it!
      return node.stateNode;
    }
  }
}

需要注意的是,能渲染的真实节点的结构跟fiber结构并不会完全对应,可能会存在一些层次的包装,所以需要进行比较复杂的查找。

第四步则是正式的插入节点了:

javascript
if (isContainer) {
  insertOrAppendPlacementNodeIntoContainer(finishedWork, before, parent);
} else {
  insertOrAppendPlacementNode(finishedWork, before, parent);
}

Update

当标记为Update时,执行commitWork

当为函数组件时

javascript
commitHookEffectListUnmount(
  HookLayout | HookHasEffect,
  finishedWork,
  finishedWork.return,
);

触发useLayoutEffectdestroy方法。

当为普通真实节点时

javascript
const updatePayload: null | UpdatePayload = (finishedWork.updateQueue: any);
// 如果有属性被更新了
finishedWork.updateQueue = null;
// 更新真实节点的属性
commitUpdate(
  instance,
  updatePayload,
  type,
  oldProps,
  newProps,
  finishedWork,
);

如果属性有更新,那么会调用commitUpdateDOM属性处理的方法)更新真实DOM上的属性,如style,href等等。

layout 阶段

commitLayoutEffects

commitLayoutEffects调用commitLayoutEffects_begin方法,还是一样,找到第一个不是LayoutMask的子节点,然后向上执行commitLayoutEffectOnFiber方法。

commitLayoutEffectOnFiber

函数组件

为函数组件时,执行commitHookEffectListMount方法,也就是useLayoutEffectcreate方法。

javascript
commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);

class 组件

如果flags存在Update,说明定义了生命周期函数。当currentnull的时候,说明是挂载阶段,调用componentDidMount方法:

javascript
instance.componentDidMount();

否则说明是更新阶段,调用componentDidUpdate方法:

javascript
instance.componentDidUpdate(
  prevProps,
  prevState,
  instance.__reactInternalSnapshotBeforeUpdate,
);

最后,state更新完成之后,执行update上的回调函数:

javascript
if (updateQueue !== null) {
  commitUpdateQueue(finishedWork, updateQueue, instance);
}

export function commitUpdateQueue<State>(
  finishedWork: Fiber,
  finishedQueue: UpdateQueue<State>,
  instance: any,
): void {
  const effects = finishedQueue.effects;
  finishedQueue.effects = null;
  if (effects !== null) {
    // 依次执行已经update了的回调函数
    for (let i = 0;i < effects.length;i++) {
      const effect = effects[i];
      const callback = effect.callback;
      if (callback !== null) {
        effect.callback = null;
        callCallback(callback, instance);
      }
    }
  }
}

最后

在更新完成之后,更改root.current指向,相当于交换了currentworkInProgress

javascript
root.current = finishedWork;

然后再次进行调度:

javascript
ensureRootIsScheduled(root, now());

如果还有未完成的更新,即优先级不够的更新,那么这里会被继续调度进行更新。

如果当前更新中包含useEffect,并且lanes中含有同步lane,那么需要立即执行flushPassiveEffect

javascript
if (
  includesSomeLane(pendingPassiveEffectsLanes, SyncLane) &&
  root.tag !== LegacyRoot
) {
  flushPassiveEffects();
}

相比于schedule执行flushPassiveEffects,这里执行更靠前。

最后还会执行flushSyncCallbacks

javascript
flushSyncCallbacks();

该函数用于将立即执行layout里新添加的任务。