资源一览

React 内部函数调用关系草图

Mount 阶段大致如下,可供参考,缩进意为被上一层调用,标记!!!的注释意为核心代码。

ReactDOM.render;
legacyRenderSubtreeIntoContainer;
legacyCreateRootFromDOMContainer;
createRoot;
new ReactDOMRoot();
createRootImpl;
createContainer;
createFiberRoot;
new FiberRootNode();
createHostRootFiber;
createFiber;
new FiberNode();
initializeUpdateQueue;
unbatchedUpdates;
updateContainer;
requestEventTime;
requestUpdateLane;
getContextForSubtree;
createUpdate;
enqueueUpdate;
scheduleUpdateOnFiber; // !!! reconcile开始
markUpdateLaneFromFiberToRoot; // 从当前节点到根节点,依次更新节点的Lane
markRootUpdated; // 根节点标记为:存在pending update
performSyncWorkOnRoot; // !!! reconcile开始
getNextLanes;
renderRootSync;
prepareFreshStack; // 初始化Work Loop必需的所有变量
createWorkInProgress;
createFiber;
enqueueInterleavedUpdates;
pushDispatcher; // 设置一个ReactCurrentDispatcher,主要用于处理Hooks
workLoopSync; // !!! 循环处理unitOfWork,正在处理的work称为workInProgress
performUnitOfWork;
beginWork; // !!! 沿child构建Fiber,直到child为null
updateHostRoot; // !!! 不同的任务有不同的函数去处理
pushHostRootContext;
cloneUpdateQueue;
processUpdateQueue; // 处理update queue,计算出最终state
reconcileChildren; // 处理children
mountChildFibers;
reconcileChildFibers;
placeSingleChild;
reconcileSingleElement;
completeUnitOfWork; // !!! 将sibling设为workInProgress,没有则沿return找父节点的sibling,直到return为null
completeWork;
resetContextDependencies;
popDispatcher; // render结束,还原Dispatcher
commitRoot;
commitRootImpl;
commitBeforeMutationEffects; // !!! 1阶段,Before Mutation,执行生命周期函数
prepareForCommit;
commitBeforeMutationEffects_begin;
commitBeforeMutationEffects_complete;
commitBeforeMutationEffectsOnFiber; // 执行getSnapshotBeforeUpdate
commitMutationEffects; // !!! 2阶段,根据fiber.flags更新DOM
commitMutationEffects_begin;
commitMutationEffects_complete;
commitDetachRef; // 解除ref
commitAttachRef; // 重新设置ref
commitPlacement;
insertOrAppendPlacementNodeIntoContainer; // React DOM Api
insertOrAppendPlacementNode; // React DOM Api
commitWork;
commitHookEffectListUnmount;
commitContainer;
replaceContainerChildren; // React DOM Api
commitLayoutEffects; // !!! 3阶段,执行layout
commitLayoutEffects_begin; // componentDidMount
safelyCallCommitHookLayoutEffectListMount;
commitHookEffectListMount; // HookLayout
ensureRootIsScheduled;
entangleTransitions;
getPublicRootInstance;

Update 阶段(setState,其他类似)

Component.prototype.setState;
classComponentUpdater.enqueueSetState;
createUpdate;
enqueueUpdate;
scheduleUpdateOnFiber; // !!!核心函数
ensureRootIsScheduled;
performConcurrentWorkOnRoot;
renderRootConcurrent;
pushDispatcher;
workLoopConcurrent; // !!!循环处理unitOfWork,正在处理的work称为workInProgress
shouldYield; // !!!来自scheduler,为true时停止任务处理
performUnitOfWork; // 和Mount类似,只不过只处理更新阶段对应的任务,以下省略
popDispatcher;
finishConcurrentRender;
commitRoot; // 省略

Mount 基本流程

  1. React.createElement()构建出 React DOM;
  2. ReactDOM.render()是 React App 初始化的起点,以下 API 如无特殊说明,默认属于reconciler库;
    1. 构建两个互相引用的实例:fiberRoot 是整个 App 的根节点,rootFiber 是基于 ReactDOM 构建的 Fiber Tree 的根节点;
    2. updateContainer 正式开始 reconcile 过程,在此期间触发的更新以 unbatched 形式执行,主要执行两个函数:
      1. renderRootSync,进入 render 阶段,核心函数调用为:
        1. pushDispatcher,初始化一个 ReactCurrentDispatcher,内含所有 React Hook 的实现;
        2. workLoopSync,不同类型的 FiberNode 在 render 阶段对应不同的任务(例如类组件和函数组件,任务显然不同),WorkLoopSync 通过一个循环同步地处理这些任务;
        3. popDispatcher,还原 ReactCurrentDispatcher;
      2. commitRoot,进入 commit 阶段,根据 Diff 结果更新浏览器 DOM,触发对应 Effect Hook 或生命周期方法;

类组件 Update 基本流程

类组件有两种更新组件的方式:

  1. setState;
  2. forceUpdate;

这两个都是定义在 React.Component.prototype 上的方法,流程相似。

Component.prototype.setState = function (partialState, callback) {
  this.updater.enqueueSetState(this, partialState, callback, "setState");
};

Component.prototype.forceUpdate = function (callback) {
  this.updater.enqueueForceUpdate(this, callback, "forceUpdate");
};

以 setState 为例的流程:

  1. 创建 update 对象并添加进 updateQueue;
  2. 调用 scheduleUpdateOnFiber,开始处理更新:
    1. 如果是 batchedUpdates,则每次取消上个调度任务,再重新申请一个新的调度任务;
    2. 否则直接申请一个调度任务;
  3. 轮到该任务执行时,进入 workLoop

注:被调度的任务就是performConcurrentWorkOnRoot函数;

classComponentUpdater

实际调用的是this.updater.enqueueSetState,this.updater是在 render 阶段的updateClassComponent -> constructClassInstance -> adoptClassInstance函数中注入的。

function adoptClassInstance(workInProgress: Fiber, instance: any): void {
  // 注意这一行!!!instance就是ClassComponent的实例
  instance.updater = classComponentUpdater;
  workInProgress.stateNode = instance;
  // The instance needs access to the fiber so that it can schedule updates
  setInstance(instance, workInProgress);
}

对照关系:

  1. setState:实际调用 classComponentUpdater.enqueueSetState;
  2. forceUpdate:实际调用 classComponentUpdater.enqueueForceUpdate;

其实这两个方法逻辑几乎一样,唯一不同点是 setState 需要携带上 payload,也就是组件中调用 setState 时传入的 partialState,而 forceUpdate 不需要携带 payload,只是把 update.tag 置为 ForceUpdate(一个二进制数)。

const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    const fiber = getInstance(inst);
    const eventTime = requestEventTime();
    const lane = requestUpdateLane(fiber);

    const update = createUpdate(eventTime, lane);
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      update.callback = callback;
    }

    enqueueUpdate(fiber, update, lane);
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    if (root !== null) {
      entangleTransitions(root, fiber, lane);
    }
  },
  enqueueForceUpdate(inst, callback) {
    const fiber = getInstance(inst);
    const eventTime = requestEventTime();
    const lane = requestUpdateLane(fiber);

    const update = createUpdate(eventTime, lane);
    update.tag = ForceUpdate;

    if (callback !== undefined && callback !== null) {
      update.callback = callback;
    }

    enqueueUpdate(fiber, update, lane);
    const root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    if (root !== null) {
      entangleTransitions(root, fiber, lane);
    }
  },
};

Update Queue

setState/forceUpdate/react hook 等触发的更新会创建一个 update 对象,并添加到 updateQueue,这是为了实现更新批处理而设计的,批处理生效期间,多次更新按顺序加到 updateQueue 中,期间每个更新会取消上一次更新的任务调度,并申请一次新的任务调度(scheduler 提供任务调度能力),直到批处理结束,进入下一轮事件循环,被调度的任务得到执行机会,真正进入 render 阶段并通过processUpdateQueue一次性处理所有更新。

React 合成事件默认开启更新批处理,其他场景必须手动调用unstable_batchedUpdates,例如异步的回调(setTimeout、Promise.then 等)是无法批处理的,这里引申一个常见问题——setState 是异步还是同步的?

[注 1]:batched updates 参考这篇博客——React 源码解读之 Automatic Batching(在新窗口打开)

[注 2]:update queue 结构、计算过程参考这篇博客——React 源码细读-UpdateQueue 机制与 Lane 优先级模型(在新窗口打开)

setState是异步还是同步的?

合成事件与生命周期函数中是异步的,异步函数回调与原生事件中是同步的。

注意,此处同步、异步并非指 setState 在 JS 引擎层面的同步或异步,而是指 this.state 的更新能否立刻体现出来:

  1. 同步:setState 后立刻可以通过 this.state 取到更新后的 state;
  2. 异步:setState 后不能立刻取到更新后的 state;

从源码层面解释:

  1. 合成事件默认开启更新批处理,setState 执行后并不会在本次事件循环中完成 state 更新,因此批处理结束前不能获取到最新 state;
  2. 生命周期方法中的 setState 执行后,state 的赋值不是立刻执行的,因此也无法得到最新 state;

异步函数、原生事件的回调中不会应用更新批处理,因此每个 setState 都会触发一次 render、commit,因此可以 setState 后立刻获得最新 state;

scheduleUpdateOnFiber

update 会被赋予一个优先级,根据优先级选择对应的任务调度方式,SyncLane 直接调用performSyncWorkOnRoot处理。

// scheduleUpdateOnFiber
if (lane === SyncLane) {
  if (
    // Check if we're inside unbatchedUpdates
    (executionContext & LegacyUnbatchedContext) !== NoContext &&
    // Check if we're not already rendering
    (executionContext & (RenderContext | CommitContext)) === NoContext
  ) {
    // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
    // root inside of batchedUpdates should be synchronous, but layout updates
    // should be deferred until the end of the batch.
    performSyncWorkOnRoot(root);
  } else {
    //...
  }
} else {
  // Schedule other updates after in case the callback is sync.
  ensureRootIsScheduled(root, eventTime);
}

其他优先级通过ensureRootIsScheduled->scheduleCallback调度一个performConcurrentWorkOnRoot函数。

// ensureRootIsScheduled

// !!!batchedUpdates情况下,取消上一次调度
if (existingCallbackNode != null) {
  // Cancel the existing callback. We'll schedule a new one below.
  cancelCallback(existingCallbackNode);
}

// !!!申请一个新的调度任务
let newCallbackNode;
if (newCallbackPriority === SyncLane) {
  // ...
} else {
  let schedulerPriorityLevel;
  switch (lanesToEventPriority(nextLanes)) {
    case DiscreteEventPriority:
      schedulerPriorityLevel = ImmediateSchedulerPriority;
      break;
    case ContinuousEventPriority:
      schedulerPriorityLevel = UserBlockingSchedulerPriority;
      break;
    case DefaultEventPriority:
      schedulerPriorityLevel = NormalSchedulerPriority;
      break;
    case IdleEventPriority:
      schedulerPriorityLevel = IdleSchedulerPriority;
      break;
    default:
      schedulerPriorityLevel = NormalSchedulerPriority;
      break;
  }
  // !!!看这里,调度一个performConcurrentWorkOnRoot
  newCallbackNode = scheduleCallback(
    schedulerPriorityLevel,
    performConcurrentWorkOnRoot.bind(null, root)
  );
}

performConcurrentWorkOnRoot得到执行机会时,会去执行renderRootConcurrent,若已经超时则直接以同步优先级执行renderRootSync.

// performConcurrentWorkOnRoot

let exitStatus =
  shouldTimeSlice(root, lanes) &&
  (disableSchedulerTimeoutInWorkLoop || !didTimeout)
    ? renderRootConcurrent(root, lanes)
    : renderRootSync(root, lanes);

Work Loop

Work Loop 根据 Legacy/Concurrent 模式的不同,分为两套逻辑,但总的来说,不同 Fiber 节点的不同阶段各有不同的任务,二者都是为了循环处理每个 Fiber 节点对应的任务:

  1. workLoopSync
  2. workLoopConcurrent

workLoop 都是在 try catch 块中调用的,原因就是为了在 catch 块中通过handleError处理错误,如下:

// renderRootSync
do {
  try {
    workLoopSync();
    break;
  } catch (thrownValue) {
    handleError(root, thrownValue);
  }
} while (true);

// renderRootConcurrent
do {
  try {
    workLoopConcurrent();
    break;
  } catch (thrownValue) {
    handleError(root, thrownValue);
  }
} while (true);

核心逻辑中的相关变量定义:

  • workInProgress:当前正在处理的 Fiber;
  • unitOfWork:每个 Fiber 节点都有对应的一系列任务,译为工作单元;

Fiber 节点重要属性:

  • child:指向首个子节点;
  • sibling:指向右侧首个兄弟节点(此处右指的是 ReactDOM 画图后方向上的右,注意树是没有逻辑上的左右之分的,只有二叉树有);
  • return:指向父节点;
  • flags:用于在 render 阶段添加一些标记如 Update、Delete、Snapshot 等,以便 render 后续逻辑或 commit 阶段通过这些标记执行特定逻辑;
  • tag:组件类型,如 ClassComponent、FunctionComponent;

注:flags 是用二进制位表示各种标记,位运算符实现加、删、验;

异常处理机制

上文可知 handleError 就是 React 组件抛错的处理入口,该函数通过 throwException 来处理组件异常,核心逻辑:

  1. 沿 return 遍历父组件,找到最近的 ErrorBoundary,创建一个 ErrorUpdate,处理该 Update 时,将调用生命周期方法:
    1. getDerivedStateFromError
    2. componentDidCatch
  2. 如果没有找到 ErrorBoundary,则在 HostRoot 上触发一个 ErrorUpdate,该 Update 将打印一些 Error Log;

注:实现了getDerivedStateFromError或componentDidCatch的类组件称为 ErrorBoundary.

// throwException
value = createCapturedValue(value, sourceFiber);
let workInProgress = returnFiber;
do {
  switch (workInProgress.tag) {
    case HostRoot: {
      const errorInfo = value;
      workInProgress.flags |= ShouldCapture;
      const lane = pickArbitraryLane(rootRenderLanes);
      workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
      const update = createRootErrorUpdate(workInProgress, errorInfo, lane);
      enqueueCapturedUpdate(workInProgress, update);
      return;
    }
    case ClassComponent:
      // Capture and retry
      const errorInfo = value;
      const ctor = workInProgress.type;
      const instance = workInProgress.stateNode;
      if (
        (workInProgress.flags & DidCapture) === NoFlags &&
        (typeof ctor.getDerivedStateFromError === "function" ||
          (instance !== null &&
            typeof instance.componentDidCatch === "function" &&
            !isAlreadyFailedLegacyErrorBoundary(instance)))
      ) {
        workInProgress.flags |= ShouldCapture;
        const lane = pickArbitraryLane(rootRenderLanes);
        workInProgress.lanes = mergeLanes(workInProgress.lanes, lane);
        // Schedule the error boundary to re-render using updated state
        const update = createClassErrorUpdate(workInProgress, errorInfo, lane);
        enqueueCapturedUpdate(workInProgress, update);
        return;
      }
      break;
    default:
      break;
  }
  workInProgress = workInProgress.return;
} while (workInProgress !== null);

ErrorUpdate 与普通 Update 的处理类似,ErrorUpdate 可看作定制化的 Update,这里的定制指:

  1. tag 标记为 CaptureUpdate,意为抛出异常所触发的一次特殊更新;
  2. payload,将调用 getDerivedStateFromError;
  3. callback,将调用 componentDidCatch;
function createClassErrorUpdate(
  fiber: Fiber,
  errorInfo: CapturedValue<mixed>,
  lane: Lane
): Update<mixed> {
  const update = createUpdate(NoTimestamp, lane);
  update.tag = CaptureUpdate;
  const getDerivedStateFromError = fiber.type.getDerivedStateFromError;
  if (typeof getDerivedStateFromError === "function") {
    const error = errorInfo.value;
    update.payload = () => {
      logCapturedError(fiber, errorInfo);
      return getDerivedStateFromError(error);
    };
  }

  const inst = fiber.stateNode;
  if (inst !== null && typeof inst.componentDidCatch === "function") {
    update.callback = function callback() {
      if (__DEV__) {
        // ...
      }
      if (typeof getDerivedStateFromError !== "function") {
        // To preserve the preexisting retry behavior of error boundaries,
        // we keep track of which ones already failed during this batch.
        // This gets reset before we yield back to the browser.
        // TODO: Warn in strict mode if getDerivedStateFromError is
        // not defined.
        markLegacyErrorBoundaryAsFailed(this);

        // Only log here if componentDidCatch is the only error boundary method defined
        logCapturedError(fiber, errorInfo);
      }
      const error = errorInfo.value;
      const stack = errorInfo.stack;
      this.componentDidCatch(error, {
        componentStack: stack !== null ? stack : "",
      });
      if (__DEV__) {
        // ...
      }
    };
  } else if (__DEV__) {
    // ...
  }
  return update;
}

workLoopSync

function workLoopSync() {
  // Already timed out, so perform work without checking if we need to yield.
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

workLoopSync 很简单,就是循环处理 workInProgress,处理完一个,再将下一个 Fiber 节点赋值给 workInProgress,处理顺序可看作树的 DFS,或者将 FiberTree 看作孩子-兄弟表示法得到的二叉树(左孩子,右兄弟),则顺序就是二叉树的先序遍历:

伪代码表示:

// 1 如果child存在,则切换到child;
// 2 否则:
//   2.1 如果sibling存在,切换到sibling,跳到1;
//   2.2 否则,通过return切换到父节点,跳到2.1;

performUnitOfWork

performUnitOfWork 就是 unitOfWork 的处理函数,它的作用就是处理当前任务,并得到下个待处理的 Fiber 节点;

注意 FiberTree 是有两颗的,互相以 alternate 指向各自对应节点,这两颗树一个是 workInProgress,代表当前正在处理的页面;一个是 current,代表当前正在显示的页面;

function performUnitOfWork(unitOfWork: Fiber): void {
  // The current, flushed, state of this fiber is the alternate. Ideally
  // nothing should rely on this, but relying on it here means that we don't
  // need an additional field on the work in progress.
  const current = unitOfWork.alternate;
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    // ...
  } else {
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }

  //resetCurrentDebugFiberInDEV();
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // If this doesn't spawn new work, complete the current work.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}

beginWork

beginWork 代表任务开始处理,根据 Mount、Update 阶段的不同,以及组件类型的不同,各有其对应的处理函数,这里主要介绍 ClassComponent.

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes
): Fiber | null {
  // ...
  workInProgress.lanes = NoLanes;

  switch (workInProgress.tag) {
    // ...
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes
      );
    }
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    // ...
  }
}

updateClassComponent

updateClassComponent 相对比较简单,核心逻辑就是:

  1. constructClassInstance,组件 Mount 时调用
  2. mountClassInstance,组件 Mount 时调用
  3. updateClassInstance,组件 Update 时调用
  4. finishClassComponent,beginWork 结束时调用,获取下一个 unitOfWork;
function updateClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  nextProps: any,
  renderLanes: Lanes
) {
  if (__DEV__) {
    // ...
  }

  let hasContext;
  if (isLegacyContextProvider(Component)) {
    hasContext = true;
    pushLegacyContextProvider(workInProgress);
  } else {
    hasContext = false;
  }
  prepareToReadContext(workInProgress, renderLanes);

  const instance = workInProgress.stateNode;
  let shouldUpdate;

  if (instance === null) {
    if (current !== null) {
      current.alternate = null;
      workInProgress.alternate = null;
      // Since this is conceptually a new fiber, schedule a Placement effect
      workInProgress.flags |= Placement;
    }
    // In the initial pass we might need to construct the instance.
    constructClassInstance(workInProgress, Component, nextProps);
    mountClassInstance(workInProgress, Component, nextProps, renderLanes);
    shouldUpdate = true;
  } else if (current === null) {
    // In a resume, we'll already have an instance we can reuse.
    shouldUpdate = resumeMountClassInstance(
      workInProgress,
      Component,
      nextProps,
      renderLanes
    );
  } else {
    shouldUpdate = updateClassInstance(
      current,
      workInProgress,
      Component,
      nextProps,
      renderLanes
    );
  }
  const nextUnitOfWork = finishClassComponent(
    current,
    workInProgress,
    Component,
    shouldUpdate,
    hasContext,
    renderLanes
  );
  if (__DEV__) {
    // ...
  }
  return nextUnitOfWork;
}

constructClassInstance

核心逻辑:

  1. 实例化类组件
  2. 通过 adoptClassInstance 注入 ReactUpdater
function constructClassInstance(
  workInProgress: Fiber,
  ctor: any,
  props: any
): any {
  let isLegacyContextConsumer = false;
  let unmaskedContext = emptyContextObject;
  let context = emptyContextObject;
  const contextType = ctor.contextType;

  if (__DEV__) {
    // ...
  }

  if (typeof contextType === "object" && contextType !== null) {
    context = readContext((contextType: any));
  } else if (!disableLegacyContext) {
    unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    const contextTypes = ctor.contextTypes;
    isLegacyContextConsumer =
      contextTypes !== null && contextTypes !== undefined;
    context = isLegacyContextConsumer
      ? getMaskedContext(workInProgress, unmaskedContext)
      : emptyContextObject;
  }

  // Instantiate twice to help detect side-effects.
  if (__DEV__) {
    // ...
  }

  const instance = new ctor(props, context);
  const state = (workInProgress.memoizedState =
    instance.state !== null && instance.state !== undefined
      ? instance.state
      : null);
  adoptClassInstance(workInProgress, instance);

  if (__DEV__) {
    // ...
  }

  // Cache unmasked context so we can avoid recreating masked context unless necessary.
  // ReactFiberContext usually updates this cache but can't for newly-created instances.
  if (isLegacyContextConsumer) {
    cacheContext(workInProgress, unmaskedContext, context);
  }

  return instance;
}

就是在 Mount 时通过该函数注入classComponentUpdater,具体机制已经介绍过。

function adoptClassInstance(workInProgress: Fiber, instance: any): void {
  instance.updater = classComponentUpdater;
  workInProgress.stateNode = instance;
  // The instance needs access to the fiber so that it can schedule updates
  setInstance(instance, workInProgress);
  if (__DEV__) {
    instance._reactInternalInstance = fakeInternalInstance;
  }
}

mountClassInstance

主要逻辑就是触发生命周期函数:

  1. getDerivedStateFromProps()
  2. UNSAFE_componentWillMount()
function mountClassInstance(
  workInProgress: Fiber,
  ctor: any, // constructor缩写
  newProps: any,
  renderLanes: Lanes
): void {
  // ...

  const instance = workInProgress.stateNode;
  instance.props = newProps;
  instance.state = workInProgress.memoizedState;
  instance.refs = emptyRefsObject;

  initializeUpdateQueue(workInProgress);

  const contextType = ctor.contextType;
  if (typeof contextType === "object" && contextType !== null) {
    instance.context = readContext(contextType);
  } else if (disableLegacyContext) {
    instance.context = emptyContextObject;
  } else {
    const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    instance.context = getMaskedContext(workInProgress, unmaskedContext);
  }

  if (__DEV__) {
    // ...
  }

  instance.state = workInProgress.memoizedState;

  // 调用定义在组件构造函数上的static getDerivedStateFromProps()
  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  if (typeof getDerivedStateFromProps === "function") {
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps
    );
    instance.state = workInProgress.memoizedState;
  }

  // In order to support react-lifecycles-compat polyfilled components,
  // Unsafe lifecycles should not be invoked for components using the new APIs.
  if (
    typeof ctor.getDerivedStateFromProps !== "function" &&
    typeof instance.getSnapshotBeforeUpdate !== "function" &&
    (typeof instance.UNSAFE_componentWillMount === "function" ||
      typeof instance.componentWillMount === "function")
  ) {
    callComponentWillMount(workInProgress, instance);
    // If we had additional state updates during this life-cycle, let's
    // process them now.
    processUpdateQueue(workInProgress, newProps, instance, renderLanes);
    instance.state = workInProgress.memoizedState;
  }

  if (typeof instance.componentDidMount === "function") {
    let fiberFlags: Flags = Update;
    if (enableSuspenseLayoutEffectSemantics) {
      fiberFlags |= LayoutStatic;
    }
    // ...
    workInProgress.flags |= fiberFlags;
  }
}

updateClassInstance

该函数较为复杂,只有更新类组件时调用,主要作用:

  1. 直接调用部分生命周期方法;
  2. 检测部分生命周期方法,并在 fiber 上添加特定 flags,以便在 commit 阶段调用对应生命周期方法;
  3. processUpdateQueue 计算 updates 并得到 state;
  4. 根据 props、state 的全等比较,以及 shouldComponentUpdate 的调用结果,返回一个 shouldUpdate 布尔值,指示是否抛弃本次更新;

该函数将触发以下生命周期方法:

  1. UNSAFE_componentWillReceiveProps
  2. getDerivedStateFromProps
  3. shouldComponentUpdate
  4. UNSAFE_componentWillUpdate

该函数将添加 flags,然后再在 commit 阶段触发生命周期方法:

  1. componentDidUpdate,Flag:Update;
  2. getSnapshotBeforeUpdate,Flag:Snapshot;
function updateClassInstance(
  current: Fiber,
  workInProgress: Fiber,
  ctor: any,
  newProps: any,
  renderLanes: Lanes
): boolean {
  const instance = workInProgress.stateNode;

  cloneUpdateQueue(current, workInProgress);

  const unresolvedOldProps = workInProgress.memoizedProps;
  const oldProps =
    workInProgress.type === workInProgress.elementType
      ? unresolvedOldProps
      : resolveDefaultProps(workInProgress.type, unresolvedOldProps);
  instance.props = oldProps;
  const unresolvedNewProps = workInProgress.pendingProps;

  const oldContext = instance.context;
  const contextType = ctor.contextType;
  let nextContext = emptyContextObject;
  if (typeof contextType === "object" && contextType !== null) {
    nextContext = readContext(contextType);
  } else if (!disableLegacyContext) {
    const nextUnmaskedContext = getUnmaskedContext(workInProgress, ctor, true);
    nextContext = getMaskedContext(workInProgress, nextUnmaskedContext);
  }

  const getDerivedStateFromProps = ctor.getDerivedStateFromProps;
  const hasNewLifecycles =
    typeof getDerivedStateFromProps === "function" ||
    typeof instance.getSnapshotBeforeUpdate === "function";

  // Note: During these life-cycles, instance.props/instance.state are what
  // ever the previously attempted to render - not the "current". However,
  // during componentDidUpdate we pass the "current" props.

  // In order to support react-lifecycles-compat polyfilled components,
  // Unsafe lifecycles should not be invoked for components using the new APIs.
  if (
    !hasNewLifecycles &&
    (typeof instance.UNSAFE_componentWillReceiveProps === "function" ||
      typeof instance.componentWillReceiveProps === "function")
  ) {
    if (
      unresolvedOldProps !== unresolvedNewProps ||
      oldContext !== nextContext
    ) {
      callComponentWillReceiveProps(
        workInProgress,
        instance,
        newProps,
        nextContext
      );
    }
  }

  resetHasForceUpdateBeforeProcessing();

  const oldState = workInProgress.memoizedState;
  let newState = (instance.state = oldState);
  processUpdateQueue(workInProgress, newProps, instance, renderLanes);
  newState = workInProgress.memoizedState;

  if (
    unresolvedOldProps === unresolvedNewProps &&
    oldState === newState &&
    !hasContextChanged() &&
    !checkHasForceUpdateAfterProcessing() &&
    !(
      enableLazyContextPropagation &&
      current !== null &&
      current.dependencies !== null &&
      checkIfContextChanged(current.dependencies)
    )
  ) {
    // If an update was already in progress, we should schedule an Update
    // effect even though we're bailing out, so that cWU/cDU are called.
    if (typeof instance.componentDidUpdate === "function") {
      if (
        unresolvedOldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.flags |= Update;
      }
    }
    if (typeof instance.getSnapshotBeforeUpdate === "function") {
      if (
        unresolvedOldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.flags |= Snapshot;
      }
    }
    return false;
  }

  if (typeof getDerivedStateFromProps === "function") {
    applyDerivedStateFromProps(
      workInProgress,
      ctor,
      getDerivedStateFromProps,
      newProps
    );
    newState = workInProgress.memoizedState;
  }

  const shouldUpdate =
    checkHasForceUpdateAfterProcessing() ||
    checkShouldComponentUpdate(
      workInProgress,
      ctor,
      oldProps,
      newProps,
      oldState,
      newState,
      nextContext
    ) ||
    // TODO: In some cases, we'll end up checking if context has changed twice,
    // both before and after `shouldComponentUpdate` has been called. Not ideal,
    // but I'm loath to refactor this function. This only happens for memoized
    // components so it's not that common.
    (enableLazyContextPropagation &&
      current !== null &&
      current.dependencies !== null &&
      checkIfContextChanged(current.dependencies));

  if (shouldUpdate) {
    // In order to support react-lifecycles-compat polyfilled components,
    // Unsafe lifecycles should not be invoked for components using the new APIs.
    if (
      !hasNewLifecycles &&
      (typeof instance.UNSAFE_componentWillUpdate === "function" ||
        typeof instance.componentWillUpdate === "function")
    ) {
      if (typeof instance.componentWillUpdate === "function") {
        instance.componentWillUpdate(newProps, newState, nextContext);
      }
      if (typeof instance.UNSAFE_componentWillUpdate === "function") {
        instance.UNSAFE_componentWillUpdate(newProps, newState, nextContext);
      }
    }
    if (typeof instance.componentDidUpdate === "function") {
      workInProgress.flags |= Update;
    }
    if (typeof instance.getSnapshotBeforeUpdate === "function") {
      workInProgress.flags |= Snapshot;
    }
  } else {
    // If an update was already in progress, we should schedule an Update
    // effect even though we're bailing out, so that cWU/cDU are called.
    if (typeof instance.componentDidUpdate === "function") {
      if (
        unresolvedOldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.flags |= Update;
      }
    }
    if (typeof instance.getSnapshotBeforeUpdate === "function") {
      if (
        unresolvedOldProps !== current.memoizedProps ||
        oldState !== current.memoizedState
      ) {
        workInProgress.flags |= Snapshot;
      }
    }

    // If shouldComponentUpdate returned false, we should still update the
    // memoized props/state to indicate that this work can be reused.
    workInProgress.memoizedProps = newProps;
    workInProgress.memoizedState = newState;
  }

  // Update the existing instance's state, props, and context pointers even
  // if shouldComponentUpdate returns false.
  instance.props = newProps;
  instance.state = newState;
  instance.context = nextContext;

  return shouldUpdate;
}

checkShouldComponentUpdate 中将触发 shouldComponentUpdate 生命周期方法;

注意,对于 PureComponent,shouldComponentUpdate 可以不提供实现,此时默认启用 props、state 的浅比较来判断是否抛弃本次更新;

function checkShouldComponentUpdate(
  workInProgress,
  ctor,
  oldProps,
  newProps,
  oldState,
  newState,
  nextContext
) {
  const instance = workInProgress.stateNode;
  if (typeof instance.shouldComponentUpdate === "function") {
    if (__DEV__) {
      // ...
    }
    const shouldUpdate = instance.shouldComponentUpdate(
      newProps,
      newState,
      nextContext
    );

    if (__DEV__) {
      //...
    }

    return shouldUpdate;
  }

  if (ctor.prototype && ctor.prototype.isPureReactComponent) {
    return (
      !shallowEqual(oldProps, newProps) || !shallowEqual(oldState, newState)
    );
  }

  return true;
}

finishClassComponent

该函数在 Mount 或 Update 之后调用,主要作用:

  1. 添加 Ref flag,以便在 commit 阶段更新组件 ref;
  2. 当抛出错误时卸载所有子组件;
  3. 无错误时,调用生命周期方法 render 获取 ReactDOM(ReactElements),进而执行 reconcile 逻辑;
  4. 返回 child fiber,作为下一个 unitOfWork;
function finishClassComponent(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: any,
  shouldUpdate: boolean,
  hasContext: boolean,
  renderLanes: Lanes
) {
  // Refs should update even if shouldComponentUpdate returns false
  markRef(current, workInProgress);

  const didCaptureError = (workInProgress.flags & DidCapture) !== NoFlags;

  if (!shouldUpdate && !didCaptureError) {
    // Context providers should defer to sCU for rendering
    if (hasContext) {
      invalidateContextProvider(workInProgress, Component, false);
    }

    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }

  const instance = workInProgress.stateNode;

  // Rerender
  ReactCurrentOwner.current = workInProgress;
  let nextChildren;
  if (
    didCaptureError &&
    typeof Component.getDerivedStateFromError !== "function"
  ) {
    // If we captured an error, but getDerivedStateFromError is not defined,
    // unmount all the children. componentDidCatch will schedule an update to
    // re-render a fallback. This is temporary until we migrate everyone to
    // the new API.
    // TODO: Warn in a future release.
    nextChildren = null;

    if (enableProfilerTimer) {
      // ...
    }
  } else {
    if (__DEV__) {
      // ...
    } else {
      nextChildren = instance.render();
    }
  }

  // React DevTools reads this flag.
  workInProgress.flags |= PerformedWork;
  if (current !== null && didCaptureError) {
    // If we're recovering from an error, reconcile without reusing any of
    // the existing children. Conceptually, the normal children and the children
    // that are shown on error are two different sets, so we shouldn't reuse
    // normal children even if their identities match.
    forceUnmountCurrentAndReconcile(
      current,
      workInProgress,
      nextChildren,
      renderLanes
    );
  } else {
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  }

  // Memoize state using the values we just used to render.
  // TODO: Restructure so we never read values from the instance.
  workInProgress.memoizedState = instance.state;

  // The context might have changed so we need to recalculate it.
  if (hasContext) {
    invalidateContextProvider(workInProgress, Component, true);
  }

  return workInProgress.child;
}

reconcileChildren

仍然是通过简单的 current 是否为 null 判断当前是 Mount 还是 Update,mountChildFibers 用于创建 ReactDOM 中子元素的 Fiber 节点,reconcileChildren 用于执行 Diff,根据 Diff 结果在 Fiber 节点上添加 flags,commit 阶段将根据 flags 决定如何更新浏览器 DOM.

function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes
) {
  if (current === null) {
    // If this is a fresh new component that hasn't been rendered yet, we
    // won't update its child set by applying minimal side-effects. Instead,
    // we will add them all to the child before it gets rendered. That means
    // we can optimize this reconciliation pass by not tracking side-effects.
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes
    );
  } else {
    // If the current child is the same as the work in progress, it means that
    // we haven't yet started any work on these children. Therefore, we use
    // the clone algorithm to create a copy of all the current children.

    // If we had any progressed work already, that is invalid at this point so
    // let's throw it out.
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes
    );
  }
}

mountChildFibers 和 reconcileChildFibers 将根据 children 的不同类型(例如 Fragment、Array、纯文本、HTMLElement 等)调用不同的处理逻辑;

function reconcileChildFibers(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  newChild: any,
  lanes: Lanes
): Fiber | null {
  // This function is not recursive.
  // If the top level item is an array, we treat it as a set of children,
  // not as a fragment. Nested arrays on the other hand will be treated as
  // fragment nodes. Recursion happens at the normal flow.

  // Handle top level unkeyed fragments as if they were arrays.
  // This leads to an ambiguity between <>{[...]}</> and <>...</>.
  // We treat the ambiguous cases above the same.
  const isUnkeyedTopLevelFragment =
    typeof newChild === "object" &&
    newChild !== null &&
    newChild.type === REACT_FRAGMENT_TYPE &&
    newChild.key === null;
  if (isUnkeyedTopLevelFragment) {
    newChild = newChild.props.children;
  }

  // Handle object types
  if (typeof newChild === "object" && newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE:
        return placeSingleChild(
          reconcileSingleElement(
            returnFiber,
            currentFirstChild,
            newChild,
            lanes
          )
        );
      case REACT_PORTAL_TYPE:
        return placeSingleChild(
          reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes)
        );
      case REACT_LAZY_TYPE:
        if (enableLazyElements) {
          const payload = newChild._payload;
          const init = newChild._init;
          // TODO: This function is supposed to be non-recursive.
          return reconcileChildFibers(
            returnFiber,
            currentFirstChild,
            init(payload),
            lanes
          );
        }
    }

    if (isArray(newChild)) {
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes
      );
    }

    if (getIteratorFn(newChild)) {
      return reconcileChildrenIterator(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes
      );
    }

    throwOnInvalidObjectType(returnFiber, newChild);
  }

  if (typeof newChild === "string" || typeof newChild === "number") {
    return placeSingleChild(
      reconcileSingleTextNode(
        returnFiber,
        currentFirstChild,
        "" + newChild,
        lanes
      )
    );
  }

  if (__DEV__) {
    // ...
  }
  if (typeof newChild === "undefined" && !isUnkeyedTopLevelFragment) {
    // If the new child is undefined, and the return fiber is a composite
    // component, throw an error. If Fiber return types are disabled,
    // we already threw above.
    switch (returnFiber.tag) {
      case ClassComponent: {
        if (__DEV__) {
          const instance = returnFiber.stateNode;
          if (instance.render._isMockFunction) {
            // We allow auto-mocks to proceed as if they're returning null.
            break;
          }
        }
      }
      // Intentionally fall through to the next case, which handles both
      // functions and classes
      // eslint-disable-next-lined no-fallthrough
      case FunctionComponent:
      case ForwardRef:
      case SimpleMemoComponent: {
        invariant(
          false,
          "%s(...): Nothing was returned from render. This usually means a " +
            "return statement is missing. Or, to render nothing, " +
            "return null.",
          getComponentNameFromFiber(returnFiber) || "Component"
        );
      }
    }
  }

  // Remaining cases are all treated as empty.
  return deleteRemainingChildren(returnFiber, currentFirstChild);
}

[注]:Diff 过程可参考这篇博客:老生常谈 React 的 diff 算法原理-面试版(在新窗口打开)

workLoopConcurrent

function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

显然 Concurrent 模式下,与 Legacy 模式唯一的不同点就在于 shouldYield 函数的调用,shouldYield 是 scheduler 导出的函数,简单来说,当时间分片已经耗尽时,就会通过 shouldYield 返回一个 true,使 Work Loop 停止处理,等待下一时间分片。

performUnitOfWork逻辑同上。

FunctionComponent的 Mount 与 Update

函数组件在 beginWork 里走 updateFunctionComponent。它比类组件短很多,因为函数组件没有实例、没有生命周期,所有状态都挂在 Fiber 的 hooks 链表上:

case FunctionComponent: {
  const Component = workInProgress.type;
  const unresolvedProps = workInProgress.pendingProps;
  const resolvedProps =
    workInProgress.elementType === Component
      ? unresolvedProps
      : resolveDefaultProps(Component, unresolvedProps);
  return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes);
}

updateFunctionComponent 做三件事:准备 context、调用 renderWithHooks 拿到 children、reconcileChildren:

function updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes) {
  // 1. 读取 context(disableLegacyContext 为 true 时跳过)
  let context;
  if (!disableLegacyContext) {
    const unmaskedContext = getUnmaskedContext(workInProgress, Component, true);
    context = getMaskedContext(workInProgress, unmaskedContext);
  }

  // 2. 执行组件函数,Hooks 全部发生在这里
  prepareToReadContext(workInProgress, renderLanes);
  const nextChildren = renderWithHooks(
    current,
    workInProgress,
    Component,
    nextProps,
    context,
    renderLanes,
  );

  // 3. props/state 都没变,直接复用子节点
  if (current !== null && !didReceiveUpdate) {
    bailoutHooks(current, workInProgress, renderLanes);
    return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
  }

  workInProgress.flags |= PerformedWork;
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  return workInProgress.child;
}

关键是 renderWithHooks:

function renderWithHooks(
  current,
  workInProgress,
  Component,
  props,
  secondArg,
  nextRenderLanes,
) {
  renderLanes = nextRenderLanes;
  currentlyRenderingFiber = workInProgress;

  // 清空上一次的 hooks 与 effect 队列
  workInProgress.memoizedState = null;
  workInProgress.updateQueue = null;
  workInProgress.lanes = NoLanes;

  // 根据 current 是否存在选择 dispatcher
  ReactCurrentDispatcher.current =
    current === null || current.memoizedState === null
      ? HooksDispatcherOnMount
      : HooksDispatcherOnUpdate;

  let children = Component(props, secondArg);

  // render 阶段更新:用 do...while 重新执行组件函数
  if (didScheduleRenderPhaseUpdateDuringThisPass) {
    do {
      didScheduleRenderPhaseUpdateDuringThisPass = false;
      // ...
      children = Component(props, secondArg);
    } while (didScheduleRenderPhaseUpdateDuringThisPass);
  }

  finishRenderingHooks();
  return children;
}

Mount 和 Update 的差别,全在 dispatcher 上:

阶段dispatcheruseState 实际调用
MountHooksDispatcherOnMountmountState → mountWorkInProgressHook,创建 queue 和 dispatch
UpdateHooksDispatcherOnUpdateupdateState → updateReducer,复用 hook、处理 pending update

Mount 时 mountState 会创建 hook、初始化 memoizedState、创建 queue,并把 dispatchAction bind 到当前 fiber 和 queue 上;Update 时 updateWorkInProgressHook 从 current fiber 的链表上克隆节点,updateReducer 再合并 pending 队列算出新状态。useMemo / useCallback 的依赖比较、useEffect 的 deps 比较,也都是在这一步完成的——它们只是把结果存在不同的 hook 字段里。

// ReactFiberHooks.js
function mountState(initialState) {
  const hook = mountWorkInProgressHook();
  if (typeof initialState === "function") initialState = initialState();
  hook.memoizedState = hook.baseState = initialState;

  const queue = (hook.queue = {
    pending: null,
    interleaved: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState,
  });

  const dispatch = (queue.dispatch = dispatchAction.bind(
    null,
    currentlyRenderingFiber,
    queue,
  ));
  return [hook.memoizedState, dispatch];
}

function updateState(initialState) {
  return updateReducer(basicStateReducer, initialState);
}

bailoutHooks 和 bailoutOnAlreadyFinishedWork 是函数组件的性能关键:父组件重渲染时,如果子组件的 props 没变、state 没变、context 也没变,renderWithHooks 执行完后 didReceiveUpdate 仍是 false,就会直接跳过 reconciliation,复用旧子节点。这也是 React.memo、useMemo、useCallback 能生效的底层原因。

commit 阶段,函数组件的“生命周期”来自 updateQueue 上的 effect 环形链表:commitHookEffectListMount 依次执行 effect.create(),把返回值存进 effect.destroy;下一次更新或卸载时先执行 destroy。useLayoutEffect 在 mutation 之后、浏览器绘制之前同步执行;useEffect 则被推到 Passive 阶段,在绘制之后异步执行。

scheduler

packages/scheduler 是 React 17 里独立出来的调度器,不依赖 React,只负责三件事:优先级、任务队列、时间切片。

// scheduler/src/SchedulerPriorities.js
export const ImmediatePriority = 1;      // 立刻执行,timeout = -1
export const UserBlockingPriority = 2;   // 250ms
export const NormalPriority = 3;         // 5000ms
export const LowPriority = 4;            // 10000ms
export const IdlePriority = 5;           // 1073741823ms

React 侧用 lanes 表达优先级,进入调度前会先映射成内部的 LanePriority:

// ReactFiberWorkLoop.js
function lanesToEventPriority(lanes) {
  const lane = getHighestPriorityLane(lanes);
  if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
    return DiscreteEventPriority;
  }
  if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
    return ContinuousEventPriority;
  }
  if (includesNonIdleWork(lane)) return DefaultEventPriority;
  return IdleEventPriority;
}

ensureRootIsScheduled 会再用 lanePriorityToSchedulerPriority 把它转成 Scheduler 的 1~5。SyncLane 不走 Scheduler,直接进 syncQueue;其余 lane 才交给 scheduleCallback。

scheduleCallback 把任务包成一个 task,放进小顶堆:

function scheduleCallback(priorityLevel, callback, options) {
  const currentTime = getCurrentTime();
  const timeout = timeoutForPriorityLevel(priorityLevel); // 见上表
  const expirationTime = currentTime + timeout;
  const task = {
    id: taskIdCounter++,
    callback,
    priorityLevel,
    startTime: currentTime,
    expirationTime,
    sortIndex: expirationTime,
  };

  if (expirationTime > currentTime) {
    // 还没到期,放进 timerQueue
    push(timerQueue, task);
    if (task === peek(timerQueue)) {
      requestHostTimeout(handleTimeout, expirationTime - currentTime);
    }
  } else {
    // 已到期,放进 taskQueue
    push(taskQueue, task);
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    }
  }
  return task;
}

requestHostCallback 用 MessageChannel 把 performWorkUntilDeadline 丢到宏任务里:

const channel = new MessageChannel();
const port = channel.port2;
channel.port1.onmessage = performWorkUntilDeadline;

function performWorkUntilDeadline() {
  if (scheduledHostCallback !== null) {
    const currentTime = getCurrentTime();
    startTime = currentTime;
    const hasMoreWork = scheduledHostCallback(currentTime);
    if (hasMoreWork) {
      port.postMessage(null); // 还有任务,让出后再回来
    } else {
      scheduledHostCallback = null;
      isMessageLoopRunning = false;
    }
  }
}

workLoop 每次从 taskQueue 取堆顶执行,shouldYieldToHost() 判断是否该让出主线程:

function shouldYieldToHost() {
  const timeElapsed = getCurrentTime() - startTime;
  if (timeElapsed < frameInterval) return false; // frameInterval = 5ms
  if (enableIsInputPending) {
    // 优先响应输入
  }
  return true;
}

flushWork 在真正执行任务前会先调用 advanceTimers,把 timerQueue 里已经到期的任务搬进 taskQueue,所以“延后执行”和“立刻执行”共用同一个循环,只是入口不同。

React 17 默认的 ReactDOM.render 是 Legacy 模式:scheduleUpdateOnFiber 遇到 SyncLane 会直接 performSyncWorkOnRoot,或者在事件结束时 flushSyncCallbackQueue,整棵树一次性同步 render 完。只有 createRoot(当时的实验 API)才会走 Concurrent 模式:workLoopConcurrent 在 while (workInProgress !== null && !shouldYield()) 里逐个处理 work unit,5ms 左右让出一次,把长任务切成小段,输入和动画才有机会插进来。

顺带一提,Scheduler 没有用 requestIdleCallback:它在后台标签页里会被节流到 1fps,而且没有稳定的 50ms 预算;MessageChannel 是宏任务,能让出主线程,又不会被浏览器按空闲回调的规则摆布。

备注

  • 类组件 state 的更新发生在 mountClassInstance,被赋值为 workInProgress.memoizedState;
End of entry