Javascript

React源码解析之commitRoot整体流程概览

本文主要是介绍React源码解析之commitRoot整体流程概览,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前言
React源码解析之renderRoot概览 中,renderRoot()的最后一段的switch...case即进入到了commit阶段:

  switch (workInProgressRootExitStatus) {
    //...

    case RootErrored: {
      //...

      // If we're already rendering synchronously, commit the root in its
      // errored state.
      //commit阶段
      return commitRoot.bind(null, root);
    }
    case RootSuspended: {
      //...

      // The work expired. Commit immediately.
      //commit阶段
      return commitRoot.bind(null, root);
    }
    case RootSuspendedWithDelay: {
      //...

      // The work expired. Commit immediately.
      //commit阶段
      return commitRoot.bind(null, root);
    }
    case RootCompleted: {
      // The work completed. Ready to commit.
      //...

      //commit阶段
      return commitRoot.bind(null, root);
    }
    default: {
      invariant(false, 'Unknown root exit status.');
    }
  }
复制代码

那本篇文章就来整体看下commitRoot()/commitRootImpl()的整体流程,之后的文章再细讲内部各个function的源码。

一、commitRoot()
作用:
① 以最高优先级去执行commitRootImpl()
② 如果有脏作用的话,用一个callback回调函数去清除掉它们

源码:

function commitRoot(root) {
  //ImmediatePriority,优先级为 99,最高优先级,立即执行
  //bind函数,请看:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

  //获取调度优先级,并临时替换当前的优先级,去执行传进来的 callback
  runWithPriority(ImmediatePriority, commitRootImpl.bind(null, root));
  // If there are passive effects, schedule a callback to flush them. This goes
  // outside commitRootImpl so that it inherits the priority of the render.
  //如果还有脏作用的话,用一个 callback 回调函数去清除掉它们
  //因为是在commitRootImpl()外执行的,所以会继承 render 时的优先级
  if (rootWithPendingPassiveEffects !== null) {
    //获取render 时的优先级
    //请看:[React源码解析之scheduleWork(上)](https://juejin.im/post/5d7fa983f265da03cf7ac048)中的「五、getCurrentPriorityLevel()」
    const priorityLevel = getCurrentPriorityLevel();
    //对callback进行包装处理,并更新调度队列的状态

    //请看[React源码解析之scheduleWork(下)](https://juejin.im/post/5d885b75f265da03e83baaa7)中的[十、scheduleSyncCallback()]的解析
    scheduleCallback(priorityLevel, () => {
      //清除脏作用
      flushPassiveEffects();
      return null;
    });
  }
  return null;
}
复制代码

解析:
(1) 执行runWithPriority(),获取调度优先级,并临时替换当前的优先级,去执行传进来的 callback

由于ImmediatePriority是最高等级的优先级,所以会立即执行commitRootImpl()方法

runWithPriority()的源码如下:

//获取调度优先级,并临时替换当前的优先级,去执行传进来的 callback
export function runWithPriority<T>(
  reactPriorityLevel: ReactPriorityLevel,
  fn: () => T,
): T {
  //获取调度优先级
  const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
  //临时替换当前的优先级,去执行传进来的 callback
  return Scheduler_runWithPriority(priorityLevel, fn);
}
复制代码

reactPriorityToSchedulerPriority()的源码如下:

//获取调度优先级
function reactPriorityToSchedulerPriority(reactPriorityLevel) {
  switch (reactPriorityLevel) {
    case ImmediatePriority:
      return Scheduler_ImmediatePriority;
    case UserBlockingPriority:
      return Scheduler_UserBlockingPriority;
    case NormalPriority:
      return Scheduler_NormalPriority;
    case LowPriority:
      return Scheduler_LowPriority;
    case IdlePriority:
      return Scheduler_IdlePriority;
    default:
      invariant(false, 'Unknown priority level.');
  }
}
复制代码

Scheduler_runWithPriority()unstable_runWithPriority(),源码如下:

//临时替换当前的优先级,去执行传进来的 callback
function unstable_runWithPriority(priorityLevel, eventHandler) {
  //默认是 NormalPriority
  switch (priorityLevel) {
    case ImmediatePriority:
    case UserBlockingPriority:
    case NormalPriority:
    case LowPriority:
    case IdlePriority:
      break;
    default:
      priorityLevel = NormalPriority;
  }

  //缓存当前优先级 currentPriorityLevel
  var previousPriorityLevel = currentPriorityLevel;
  //临时替换优先级,去执行 eventHandler()
  currentPriorityLevel = priorityLevel;
  //try 里 return 了,还是会执行 finally 内的语句
  try {
    return eventHandler();
  } finally {
    //恢复当前优先级为之前的优先级
    currentPriorityLevel = previousPriorityLevel;
  }
}
复制代码

(2) 执行完runWithPriority()后,如果还有脏作用的话,用一个callback回调函数去清除掉它们

getCurrentPriorityLevel()的作用是:获取render时的优先级,
具体请看:
React源码解析之scheduleWork(上)中的五、getCurrentPriorityLevel()

scheduleCallback()的作用是:对callback进行包装处理,并更新调度队列的状态
具体请看:
React源码解析之scheduleWork(下)中的十、scheduleSyncCallback()

③ 这个callback回调函数就是flushPassiveEffects(),作用是:清除脏作用
具体的源码解析,我们在以后的文章里讲。

(3) commitRoot()的核心函数是commitRootImpl(),接下来就来看下它的整体结构及流程。

二、commitRootImpl()
作用:
(1) 根据effect链判断是否进行commit
① 当执行commit时,进行before mutationmutationlayout三个子阶段
② 否则快速过掉commit阶段,走个 report 流程

(2) 判断本次commit是否会产生新的更新,也就是脏作用,如果有脏作用则处理它

(3) 检查目标fiber是否有剩余的work要做
① 如果有剩余的work的话,执行这些调度任务
② 没有的话,说明也没有报错,清除「错误边界」

(4) 刷新同步队列

源码:

function commitRootImpl(root) {
  //清除脏作用
  flushPassiveEffects();
  //dev 代码可不看
  //flushRenderPhaseStrictModeWarningsInDEV();
  //flushSuspensePriorityWarningInDEV();

  //===context判断====
  invariant(
    (executionContext & (RenderContext | CommitContext)) === NoContext,
    'Should not already be working.',
  );

  //调度完的任务
  const finishedWork = root.finishedWork;
  //调度完的优先级
  const expirationTime = root.finishedExpirationTime;
  //表示该节点没有要更新的任务,直接 return
  if (finishedWork === null) {
    return null;
  }
  //赋值给变量 finishedWork、expirationTime 后重置成初始值
  //因为下面在对finishedWork、expirationTime 进行 commit后,任务就完成了
  root.finishedWork = null;
  root.finishedExpirationTime = NoWork;

  //error 判断
  invariant(
    finishedWork !== root.current,
    'Cannot commit the same tree as before. This error is likely caused by ' +
      'a bug in React. Please file an issue.',
  );

  // commitRoot never returns a continuation; it always finishes synchronously.
  // So we can clear these now to allow a new callback to be scheduled.
  //commitRoot 是最后阶段,不会再被异步调用了,所以会清除 callback 相关的属性
  root.callbackNode = null;
  root.callbackExpirationTime = NoWork;

  //计时器,可跳过
  startCommitTimer();

  // Update the first and last pending times on this root. The new first
  // pending time is whatever is left on the root fiber.
  //目标节点的更新优先级
  const updateExpirationTimeBeforeCommit = finishedWork.expirationTime;
  //子节点的更新优先级,也就是所有子节点中优先级最高的任务
  //关于 childExpirationTime,请看:https://juejin.im/post/5dcdfee86fb9a01ff600fe1d
  const childExpirationTimeBeforeCommit = finishedWork.childExpirationTime;
  //获取优先级最高的 expirationTime
  const firstPendingTimeBeforeCommit =
    childExpirationTimeBeforeCommit > updateExpirationTimeBeforeCommit
      ? childExpirationTimeBeforeCommit
      : updateExpirationTimeBeforeCommit;
  //firstPendingTime即优先级最高的任务的 expirationTime
  root.firstPendingTime = firstPendingTimeBeforeCommit;
  //如果firstPendingTime<lastPendingTime的话,一般意味着所有的更新任务都已经完成了,更新lastPendingTime
  if (firstPendingTimeBeforeCommit < root.lastPendingTime) {
    // This usually means we've finished all the work, but it can also happen
    // when something gets downprioritized during render, like a hidden tree.
    root.lastPendingTime = firstPendingTimeBeforeCommit;
  }
  //如果目标节点root就是正在更新的节点 workInProgressRoot 的话
  //将相关值置为初始值,因为接下来会完成它的更新操作
  if (root === workInProgressRoot) {
    // We can reset these now that they are finished.
    workInProgressRoot = null;
    workInProgress = null;
    renderExpirationTime = NoWork;
  } else {
    // This indicates that the last root we worked on is not the same one that
    // we're committing now. This most commonly happens when a suspended root
    // times out.
  }

  // Get the list of effects.
  //获取 effect 链
  let firstEffect;
  //如果RootFiber 的 effectTag 有值的话,也就是说RootFiber也要commit的话
  //将它的 finishedWork 也插入到 effect 链上,放到effect 链的最后 lastEffect.nextEffect 上
  if (finishedWork.effectTag > PerformedWork) {
    // A fiber's effect list consists only of its children, not itself. So if
    // the root has an effect, we need to add it to the end of the list. The
    // resulting list is the set that would belong to the root's parent, if it
    // had one; that is, all the effects in the tree including the root.
    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;
  }

  //effect 链上第一个需要更新的 fiber 对象
  if (firstEffect !== null) {
    //=======context 相关,暂时跳过=========
    // const prevExecutionContext = executionContext;
    // executionContext |= CommitContext;
    // let prevInteractions: Set<Interaction> | null = null;
    // if (enableSchedulerTracing) {
    //   prevInteractions = __interactionsRef.current;
    //   __interactionsRef.current = root.memoizedInteractions;
    // }

    // Reset this to null before calling lifecycles
    ReactCurrentOwner.current = null;

    // The commit phase is broken into several sub-phases. We do a separate pass
    // of the effect list for each phase: all mutation effects come before all
    // layout effects, and so on.
    // 提交阶段分为几个子阶段。我们对每个阶段的效果列表进行单独的遍历:所有的mutation(突变)效果都在所有的layout效果之前

    // The first phase a "before mutation" phase. We use this phase to read the
    // state of the host tree right before we mutate it. This is where
    // getSnapshotBeforeUpdate is called.
    //第一个子阶段是「在mutation突变之前」阶段,在这个阶段 React 会读取 fiber 树的 state 状态,
    //也是用 getSnapshotBeforeUpdate 命名的原因

    //标记开始进行「before mutation」子阶段了
    startCommitSnapshotEffectsTimer();
    //更新当前选中的DOM节点,一般为 document.activeElement || document.body
    prepareForCommit(root.containerInfo);
    nextEffect = firstEffect;
    //===========第一个 while 循环==============
    do {
      if (__DEV__) {
        //删除了 dev 代码
      } else {
        try {
          //调用 classComponent 上的生命周期方法 getSnapshotBeforeUpdate
          //关于getSnapshotBeforeUpdate,请看:https://zh-hans.reactjs.org/docs/react-component.html#getsnapshotbeforeupdate
          commitBeforeMutationEffects();
        } catch (error) {
          invariant(nextEffect !== null, 'Should be working on an effect.');
          captureCommitPhaseError(nextEffect, error);
          nextEffect = nextEffect.nextEffect;
        }
      }
    } while (nextEffect !== null);
    //标记「before mutation」子阶段已经结束
    stopCommitSnapshotEffectsTimer();

    //======profiler相关,暂时跳过======
    if (enableProfilerTimer) {
      // Mark the current commit time to be shared by all Profilers in this
      // batch. This enables them to be grouped later.
      recordCommitTime();
    }

    // The next phase is the mutation phase, where we mutate the host tree.
    //标记开始进行「mutation」子阶段了
    startCommitHostEffectsTimer();
    nextEffect = firstEffect;
    //=============第二个 while 循环=================
    do {
      if (__DEV__) {
        //删除了 dev 代码
      } else {
        try {
          //提交HostComponent的 side effect,也就是 DOM 节点的操作(增删改)
          commitMutationEffects();
        } catch (error) {
          invariant(nextEffect !== null, 'Should be working on an effect.');
          captureCommitPhaseError(nextEffect, error);
          nextEffect = nextEffect.nextEffect;
        }
      }
    } while (nextEffect !== null);
    //标记「mutation」子阶段已经结束
    stopCommitHostEffectsTimer();
    //当进行 DOM 操作时,比如删除,可能会丢失选中 DOM 的焦点,此方法能保存丢失的值
    resetAfterCommit(root.containerInfo);

    // The work-in-progress tree is now the current tree. This must come after
    // the mutation phase, so that the previous tree is still current during
    // componentWillUnmount, but before the layout phase, so that the finished
    // work is current during componentDidMount/Update.

    //在「mutation」子阶段后,正在进行的fiber树(work-in-progress tree)就成了 current tree
    //以便在 componentWillUnmount 期间,保证 先前的 fiber 树是 current tree
    //以便在「layout」子阶段之前,保证 work-in-progress 的 finishedWork 是 current

    //没看懂注释,大概意思应该是随着不同子阶段的进行,及时更新 root.current,也就是当前的 fiber 树更新成正在执行 commit 的 fiber 树
    root.current = finishedWork;

    // The next phase is the layout phase, where we call effects that read
    // the host tree after it's been mutated. The idiomatic use case for this is
    // layout, but class component lifecycles also fire here for legacy reasons.
    //标记开始进行「layout」子阶段了
    //这个阶段会触发所有组件的生命周期(lifecycles)的提交
    startCommitLifeCyclesTimer();
    nextEffect = firstEffect;
    //=============第三个 while 循环==========================
    do {
      if (__DEV__) {
        //删除了 dev 代码
      } else {
        try {
          //commit lifecycles,也就是触发生命周期的 api
          commitLayoutEffects(root, expirationTime);
        } catch (error) {
          invariant(nextEffect !== null, 'Should be working on an effect.');
          captureCommitPhaseError(nextEffect, error);
          nextEffect = nextEffect.nextEffect;
        }
      }
    } while (nextEffect !== null);
    //标记「layout」子阶段已经结束
    stopCommitLifeCyclesTimer();
    //正在 commit 的 effect 置为 null,表示 commit 结束
    nextEffect = null;

    // Tell Scheduler to yield at the end of the frame, so the browser has an
    // opportunity to paint.
    //React 占用的资源已结束,告知浏览器可以去绘制 ui 了
    requestPaint();

    //=======暂时跳过=============
    if (enableSchedulerTracing) {
      __interactionsRef.current = ((prevInteractions: any): Set<Interaction>);
    }
    executionContext = prevExecutionContext;
  }
  //如果 effect 链没有需要更新的 fiber 对象
  else {
    // No effects.
    root.current = finishedWork;
    // Measure these anyway so the flamegraph explicitly shows that there were
    // no effects.
    // TODO: Maybe there's a better way to report this.

    //快速过掉 commit 阶段,走个 report 流程
    startCommitSnapshotEffectsTimer();
    stopCommitSnapshotEffectsTimer();
    if (enableProfilerTimer) {
      recordCommitTime();
    }
    startCommitHostEffectsTimer();
    stopCommitHostEffectsTimer();
    startCommitLifeCyclesTimer();
    stopCommitLifeCyclesTimer();
  }
  //标记 commit 阶段结束
  stopCommitTimer();
  //判断本次 commit 是否会产生新的更新,也就是脏作用
  const rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
  //如果有脏作用的处理
  if (rootDoesHavePassiveEffects) {
    // This commit has passive effects. Stash a reference to them. But don't
    // schedule a callback until after flushing layout work.
    rootDoesHavePassiveEffects = false;
    rootWithPendingPassiveEffects = root;
    pendingPassiveEffectsExpirationTime = expirationTime;
  }

  // Check if there's remaining work on this root
  //检查是否有剩余的 work
  const remainingExpirationTime = root.firstPendingTime;
  //如果有剩余的 work 的话
  if (remainingExpirationTime !== NoWork) {
    //计算当前时间
    const currentTime = requestCurrentTime();
    //通过 expirationTime 推断优先级
    const priorityLevel = inferPriorityFromExpirationTime(
      currentTime,
      remainingExpirationTime,
    );

    if (enableSchedulerTracing) {
      //render 阶段衍生的 work,可能指新的 update 或者新的 error
      if (spawnedWorkDuringRender !== null) {
        const expirationTimes = spawnedWorkDuringRender;
        spawnedWorkDuringRender = null;
        //循环执行 scheduleInteractions
        for (let i = 0; i < expirationTimes.length; i++) {
          //与schedule的交互
          //请看:[React源码解析之scheduleWork(上)](https://juejin.im/post/5d7fa983f265da03cf7ac048)中的「六、schedulePendingInteractions()」
          scheduleInteractions(
            root,
            expirationTimes[i],
            root.memoizedInteractions,
          );
        }
      }
    }
    // 同步调用callback
    // 流程是在root上存取callback和expirationTime,
    // 当新的callback调用时,比较更新expirationTime

    //请看:[React源码解析之scheduleWork(下)](https://juejin.im/post/5d885b75f265da03e83baaa7)中的「八、scheduleCallbackForRoot()」
    scheduleCallbackForRoot(root, priorityLevel, remainingExpirationTime);
  }
  //如果没有剩余的 work 的话,说明 commit 成功,那么就清除「错误边界」的 list
  else {
    // If there's no remaining work, we can clear the set of already failed
    // error boundaries.
    legacyErrorBoundariesThatAlreadyFailed = null;
  }

  if (enableSchedulerTracing) {
    //当本次 commit 产生的脏作用被清除后,React就可以清除已经完成的交互
    if (!rootDidHavePassiveEffects) {
      // If there are no passive effects, then we can complete the pending interactions.
      // Otherwise, we'll wait until after the passive effects are flushed.
      // Wait to do this until after remaining work has been scheduled,
      // so that we don't prematurely signal complete for interactions when there's e.g. hidden work.

      //清除已经完成的交互,如果被 suspended 挂起的话,把交互留到后续呈现
      finishPendingInteractions(root, expirationTime);
    }
  }
  //devTools 相关的,可不看
  onCommitRoot(finishedWork.stateNode, expirationTime);

  //剩余的 work 是同步任务的话
  if (remainingExpirationTime === Sync) {
    // Count the number of times the root synchronously re-renders without
    // finishing. If there are too many, it indicates an infinite update loop.

    //计算同步 re-render 重新渲染的次数,判断是否是无限循环
    if (root === rootWithNestedUpdates) {
      nestedUpdateCount++;
    } else {
      nestedUpdateCount = 0;
      rootWithNestedUpdates = root;
    }
  } else {
    nestedUpdateCount = 0;
  }
  //如果捕获到错误的话,就 throw error
  if (hasUncaughtError) {
    hasUncaughtError = false;
    const error = firstUncaughtError;
    firstUncaughtError = null;
    throw error;
  }

  //可不看
  if ((executionContext & LegacyUnbatchedContext) !== NoContext) {
    // This is a legacy edge case. We just committed the initial mount of
    // a ReactDOM.render-ed root inside of batchedUpdates. The commit fired
    // synchronously, but layout updates should be deferred until the end
    // of the batch.
    return null;
  }

  // If layout work was scheduled, flush it now.
  //「layout」阶段的任务已经被调度的话,立即清除它

  //刷新同步任务队列
  //请看:[React源码解析之scheduleWork(下)](https://juejin.im/post/5d885b75f265da03e83baaa7)中的「十二、flushSyncCallbackQueue()」
  flushSyncCallbackQueue();
  return null;
}
复制代码

解析:
虽然很长,但核心部分是那三个子阶段,也就是三个do...while循环。
整体可以看成三个部分:
(1) 准备部分
(2) commit部分(三个子阶段)
(3) 收尾部分


按源码顺序(有些我直接写进注释里了,就不讲了),看下commitRootImpl()做了些什么:

(1) 执行flushPassiveEffects(),清除脏作用

(2) 根据目标节点的更新优先级expirationTime和子节点的更新优先级childExpirationTime,来比较获取优先级最高的expirationTime,借此来判断是否所有render阶段的work都已完成。

① 关于childExpirationTime,请看:
React之childExpirationTime

(3) 判断目标 fiber自身是否也需要 commit,需要的话,则进行链表操作,把它的finishedWorkeffect链的最后——lastEffect.nextEffect

(4) 如果firstEffect不为 null 的话,说明有提交任务,则进行三个子阶段
① 第一个子阶段before mutation

执行commitBeforeMutationEffects(),本质是调用classComponent上的生命周期方法——getSnapshotBeforeUpdate()

关于getSnapshotBeforeUpdate的介绍及作用,请看:
zh-hans.reactjs.org/docs/react-…

② 第二个子阶段mutation
执行commitMutationEffects(),作用是:提交HostComponentside effect,也就是DOM 节点的操作(增删改)

关于HostComponent的相关知识,请看:
React源码解析之HostComponent的更新(上)

React源码解析之HostComponent的更新(下)

③ 第三个子阶段layout
执行commitLayoutEffects(),作用是:触发组件生命周期的api

(5) 如果firstEffect为 null 的话,说明effect链没有需要更新的fiber对象,那么就快速过掉 commit阶段,走个 report 流程

所以会看到三组startCommitXXXTimer()endCommitXXXTimer()

(6) 至此,commit 基本结束了,但是 commit 阶段可能也会产生新的 work,即remainingExpirationTime

当有剩余的 work 的话,循环它们,依次执行scheduleInteractions(),排到调度任务中去,并通过scheduleCallbackForRoot()去执行它们

① 关于scheduleInteractions(),请看:
React源码解析之scheduleWork(上)中的六、schedulePendingInteractions()

② 关于scheduleCallbackForRoot(),请看:
React源码解析之scheduleWork(下)中的八、scheduleCallbackForRoot()

(7) 最后,执行flushSyncCallbackQueue(),刷新同步任务队列

① 关于flushSyncCallbackQueue(),请看:
React源码解析之scheduleWork(下)中的十二、flushSyncCallbackQueue()

(8) 最后,我在源码上的每行都写了注释,希望能帮你熟悉commitRoot的整体流程。

GitHub
commitRoot()/commitRootImpl()
github.com/AttackXiaoJ…



(完)

这篇关于React源码解析之commitRoot整体流程概览的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!