Neil.

React v17.0.2源码

资源一览

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.enqueueSetStatethis.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;

注:实现了getDerivedStateFromErrorcomponentDidCatch的类组件称为 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

鸽。。。下次补上

scheduler

鸽。。。下次补上

备注

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