Подробное объяснение фазы фиксации исходного кода React

React.js

нажмитеВойдите в репозиторий отладки исходного кода React.

Когда фаза рендеринга завершена, это означает, что вся работа по обновлению дерева workInProgress, созданного в памяти, завершена, включая обновление волоконных узлов в дереве, маркировку diff, effectTag и сбор EffectList. На данный момент полная форма дерева workInProgress выглядит следующим образом:

current树和workInProgress树

По сравнению с текущим деревом есть отличия в их структуре, а также измененные узлы волокна есть и в дереве workInProgress, но применять эти узлы к DOM будет не циклом всего дерева, а зацикливанием связного списка effectList. что гарантирует, что работа выполняется только на узлах, которые изменились.

Таким образом, зацикливание связанного списка effectList для применения обновленного узла волокна к странице — это основная работа на этапе фиксации.

Вход в фазу фиксацииcommitRootФункция, сообщающая планировщику о необходимости планирования работы на этапе фиксации с немедленным приоритетом.

function commitRoot(root) {
  const renderPriorityLevel = getCurrentPriorityLevel();
  runWithPriority(
    ImmediateSchedulerPriority,
    commitRootImpl.bind(null, root, renderPriorityLevel),
  );
  return null;
}

Что планирует планировщикcommitRootImpl, которая является основной реализацией фазы фиксации, а вся фаза фиксации разделена на три части.

Обзор процесса фиксации

Фаза фиксации в основном касается списка эффектов, собранного в корне. Перед тем, как начнется настоящая работа, есть подготовительный этап, в основном назначение переменных и добавление root-эффекта в effectList. Затем начните работать над effectList в три этапа:

  • перед мутацией: прочитать состояние компонента перед изменением и вызвать getSnapshotBeforeUpdate для компонентов класса, чтобы мы могли получить информацию об экземпляре компонента до изменения DOM; для функциональных компонентов асинхронно отправить useEffect.
  • Мутация: для HostComponent выполните соответствующие операции DOM, для компонентов класса вызовите componentWillUnmount, для функциональных компонентов выполните функцию уничтожения useLayoutEffect.
  • макет: после завершения операции DOM прочитайте состояние компонента, вызовите жизненный цикл componentDidMount и componentDidUpdate для компонентов класса и вызовите обратный вызов setState; заполните массив выполнения эффекта useEffect для функциональных компонентов и запланируйте useEffect

До того, как мутация и макет будут взаимоисключающими для планирования useEffect функциональных компонентов, и может быть инициировано только одно планирование.

Время, когда дерево workInProgress переключается на текущее дерево, наступает после окончания мутации и до начала компоновки. Причина этого в том, что когда componentWillUnmount компонента класса вызывается на этапе мутации, Вы также можете получить информацию о компоненте перед выгрузкой; при вызове componentDidMount/Update на этапе компоновки полученная информация о компоненте обновляется.

function commitRootImpl(root, renderPriorityLevel) {

  // 进入commit阶段,先执行一次之前未执行的useEffect
  do {
    flushPassiveEffects();
  } while (rootWithPendingPassiveEffects !== null);

  // 准备阶段-----------------------------------------------

  const finishedWork = root.finishedWork;
  const lanes = root.finishedLanes;
  if (finishedWork === null) {
    return null;
  }
  root.finishedWork = null;
  root.finishedLanes = NoLanes;

  root.callbackNode = null;
  root.callbackId = NoLanes;

  // effectList的整理,将root上的effect连到effectList的末尾
  let firstEffect;
  if (finishedWork.effectTag > PerformedWork) {
    if (finishedWork.lastEffect !== null) {
      finishedWork.lastEffect.nextEffect = finishedWork;
      firstEffect = finishedWork.firstEffect;
    } else {
      firstEffect = finishedWork;
    }
  } else {
    // There is no effect on the root.
    firstEffect = finishedWork.firstEffect;
  }

  // 准备阶段结束,开始处理effectList
  if (firstEffect !== null) {

    ...

    // before mutation阶段--------------------------------
    nextEffect = firstEffect;
    do {...} while (nextEffect !== null);

    ...

    // mutation阶段---------------------------------------
    nextEffect = firstEffect;
    do {...} while (nextEffect !== null);

    // 将wprkInProgress树切换为current树
    root.current = finishedWork;

    // layout阶段-----------------------------------------
    nextEffect = firstEffect;
    do {...} while (nextEffect !== null);

    nextEffect = null;

    // 通知浏览器去绘制
    requestPaint();

  } else {
    // 没有effectList,直接将wprkInProgress树切换为current树
    root.current = finishedWork;

  }

  const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;

  // 获取尚未处理的优先级,比如之前被跳过的任务的优先级
  remainingLanes = root.pendingLanes;
  // 将被跳过的优先级放到root上的pendingLanes(待处理的优先级)上
  markRootFinished(root, remainingLanes);

  /*
  * 每次commit阶段完成后,再执行一遍ensureRootIsScheduled,确保是否还有任务需要被调度。
  * 例如,高优先级插队的更新完成后,commit完成后,还会再执行一遍,保证之前跳过的低优先级任务
  * 重新调度
  *
  * */
  ensureRootIsScheduled(root, now());

  ...

  return null;
}

В следующих разделах подробно описывается каждый из этих трех этапов.

before Mutation

Входная функция фазы beforeMutationcommitBeforeMutationEffects

nextEffect = firstEffect;
do {
  try {
    commitBeforeMutationEffects();
  } catch (error) {
    ...
  }
} while (nextEffect !== null);

Его роль в основном заключается в вызове компонентов класса.getSnapshotBeforeUpdate, для функциональных компонентов используйте асинхронную отправку useEffect.

function commitBeforeMutationEffects() {
  while (nextEffect !== null) {
    const current = nextEffect.alternate;

    ...

    const flags = nextEffect.flags;
    if ((flags & Snapshot) !== NoFlags) {
      // 通过commitBeforeMutationEffectOnFiber调用getSnapshotBeforeUpdate
      commitBeforeMutationEffectOnFiber(current, nextEffect);
    }

    if ((flags & Passive) !== NoFlags) {
      // 异步调度useEffect
      if (!rootDoesHavePassiveEffects) {
        rootDoesHavePassiveEffects = true;
        scheduleCallback(NormalSchedulerPriority, () => {
          flushPassiveEffects();
          return null;
        });
      }
    }
    nextEffect = nextEffect.nextEffect;
  }
}

Код commitBeforeMutationEffectOnFiber выглядит следующим образом

function commitBeforeMutationLifeCycles(
  current: Fiber | null,
  finishedWork: Fiber,
): void {
  switch (finishedWork.tag) {
    ...
    case ClassComponent: {
      if (finishedWork.flags & Snapshot) {
        if (current !== null) {
          const prevProps = current.memoizedProps;
          const prevState = current.memoizedState;
          const instance = finishedWork.stateNode;
          // 调用getSnapshotBeforeUpdate
          const snapshot = instance.getSnapshotBeforeUpdate(
            finishedWork.elementType === finishedWork.type
              ? prevProps
              : resolveDefaultProps(finishedWork.type, prevProps),
            prevState,
          );
          // 将返回值存储在内部属性上,方便componentDidUpdate获取
          instance.__reactInternalSnapshotBeforeUpdate = snapshot;
        }
      }
      return;
    }
    ...
  }

}

mutation

Фаза мутации будет фактически управлять узлом DOM, и задействованные операции включают добавление, удаление и модификацию. Входная функция естьcommitMutationEffects

    nextEffect = firstEffect;
    do {
      try {
        commitMutationEffects(root, renderPriorityLevel);
      } catch (error) {
        ...
        nextEffect = nextEffect.nextEffect;
      }
    } while (nextEffect !== null);

Поскольку процесс более сложный, я написал три статьи, чтобы объяснить эти три операции DOM.Если вы хотите узнать подробности, вы можете взглянуть. Статья была написана, когда 17 не была официально выпущена, поэтому версия исходного кода внутри была взята из 17.0.0-alpha0.

React и DOM-вещи — новый алгоритм узла

Эти вещи о React и DOM — алгоритм удаления узлов

React и DOM вещи - обновление узла

этап макета

Входная функция фазы макетаcommitLayoutEffects.

nextEffect = firstEffect;
do {
  try {
    commitLayoutEffects(root, lanes);
  } catch (error) {
    ...
    nextEffect = nextEffect.nextEffect;
  }
} while (nextEffect !== null);

Мы сосредоточимся только на classComponent и functionComponent. Для первого вызываются компоненты жизненного цикла componentDidMount и componentDidUpdate и вызывается обратный вызов setState; для второго заполняется массив выполнения эффекта useEffect и отправляется useEffect (конкретный принцип в этой моей статье:Разберитесь в принципах и различиях между useEffect и useLayoutEffect.объяснил).

function commitLifeCycles(
  finishedRoot: FiberRoot,
  current: Fiber | null,
  finishedWork: Fiber,
  committedLanes: Lanes,
): void {
  switch (finishedWork.tag) {
    case FunctionComponent:
    case ForwardRef:
    case SimpleMemoComponent:
    case Block: {
      // 执行useLayoutEffect的创建
      commitHookEffectListMount(HookLayout | HookHasEffect, finishedWork);

      // 填充useEffect的effect执行数组
      schedulePassiveEffects(finishedWork);
      return;
    }
    case ClassComponent: {
      const instance = finishedWork.stateNode;
      if (finishedWork.flags & Update) {
        if (current === null) {
          // 如果是初始挂载阶段,调用componentDidMount
          instance.componentDidMount();
        } else {
          // 如果是更新阶段,调用componentDidUpdate
          const prevProps =
            finishedWork.elementType === finishedWork.type
              ? current.memoizedProps
              : resolveDefaultProps(finishedWork.type, current.memoizedProps);
          const prevState = current.memoizedState;

          instance.componentDidUpdate(
            prevProps,
            prevState,
            // 将getSnapshotBeforeUpdate的结果传入
            instance.__reactInternalSnapshotBeforeUpdate,
          );
        }
      }

      // 调用setState的回调
      const updateQueue: UpdateQueue<
        *,
      > | null = (finishedWork.updateQueue: any);
      if (updateQueue !== null) {

        commitUpdateQueue(finishedWork, updateQueue, instance);
      }
      return;
    }

    ...

  }
}

Суммировать

Фаза фиксации делит обработку EffectList на три фазы, чтобы обеспечить своевременный вызов различных функций жизненного цикла. По сравнению с useEffectLayout, который выполняется синхронно, асинхронное планирование useEffect обеспечивает запись операции побочного эффекта, которая не блокирует отрисовку страницы. Кроме того, отметив необработанные приоритеты на корневом каталоге и вызвав makeRootIsScheduled, можно снова запланировать пропущенные низкоприоритетные задачи. Завершение фазы фиксации означает, что данное обновление завершено.