Skip to content

React 17 渲染流程 ​

按源码调用链梳理 React 17 中 ReactDOM.render 的同步渲染流程:从 legacyRenderSubtreeIntoContainer 到 renderRootSync 的 render 阶段,再到 commitRoot 的 commit 阶段,以及 Fiber 构建中的 reconcile 逻辑。源码基于 React 17 的实现(文件名以 .old.js 结尾的旧版文件)。

一、入口 ​

创建 React 应用的入口:

tsx
ReactDOM.render(<App />, document.getElementById("root"));

调用链一览:

text
ReactDOM.render
└─ legacyRenderSubtreeIntoContainer
   └─ updateContainer
      └─ scheduleUpdateOnFiber
         └─ performSyncWorkOnRoot
            ├─ renderRootSync → workLoopSync → performUnitOfWork
            │                    ├─ beginWork(含 reconcileChildren)
            │                    └─ completeUnitOfWork
            └─ commitRoot

二、调用链 ​

1. render ​

源码位置:packages/react-dom/src/client/ReactDOMLegacy.js

  1. 校验传入的容器
  2. 调用 legacyRenderSubtreeIntoContainer
js
export function render(
  element: React$Element<any>,
  container: Container,
  callback: ?Function,
) {
  invariant(
    isValidContainer(container),
    "Target container is not a DOM element.",
  );
  return legacyRenderSubtreeIntoContainer(
    null,
    element,
    container,
    false,
    callback,
  );
}

legacyRenderSubtreeIntoContainer 即「旧版渲染子树挂载到容器上」:因此之后有文件名分为 new 和 old 的文件,都是调用 old 文件里的内容。

2. legacyRenderSubtreeIntoContainer ​

源码位置:packages/react-dom/src/client/ReactDOMLegacy.js

该函数分为初次加载和更新两种情况:

  • 初次加载:1. 调用 legacyCreateRootFromDOMContainer 生成 root 实例;2. 调用 updateContainer
  • 更新:1. 调用 updateContainer
js
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
) {
  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;
  if (!root) {
    // Initial mount
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    fiberRoot = root._internalRoot;
    // Initial mount should not be batched.
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;
    // Update
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  return getPublicRootInstance(fiberRoot);
}

3. updateContainer ​

源码位置:packages/react-reconciler/src/ReactFiberReconciler.old.js

  1. 创建 update 并放入队列
  2. 调用 scheduleUpdateOnFiber
js
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): Lane {
  const current = container.current;
  const eventTime = requestEventTime();
  const lane = requestUpdateLane(current);

  if (enableSchedulingProfiler) {
    markRenderScheduled(lane);
  }

  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  const update = createUpdate(eventTime, lane);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  update.payload = { element };

  enqueueUpdate(current, update);
  scheduleUpdateOnFiber(current, lane, eventTime);

  return lane;
}

4. scheduleUpdateOnFiber ​

源码位置:packages/react-reconciler/src/ReactFiberWorkLoop.old.js

  1. 调用 performSyncWorkOnRoot
js
// ...
performSyncWorkOnRoot(root);

5. performSyncWorkOnRoot ​

源码位置:packages/react-reconciler/src/ReactFiberWorkLoop.old.js

js
function performSyncWorkOnRoot(root) {
  // ...
  exitStatus = renderRootSync(root, lanes);

  // ...
  commitRoot(root);

  return null;
}

performSyncWorkOnRoot 就是渲染的入口:先执行 render 阶段,把 vdom 转成 fiber,然后再执行 commit,更新到 DOM。

6. renderRootSync ​

源码位置:packages/react-reconciler/src/ReactFiberWorkLoop.old.js

  1. 不断地尝试执行 workLoopSync
  2. 如果 workLoopSync 函数执行成功,则跳出循环
  3. 如果在执行过程中出现异常,则捕获
js
function renderRootSync(root: FiberRoot, lanes: Lanes) {
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    // 创建 workInProgress
    prepareFreshStack(root, lanes);
    startWorkOnPendingInteractions(root, lanes);
  }

  do {
    try {
      // 工作轮询
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
}

7. workLoopSync ​

源码位置:packages/react-reconciler/src/ReactFiberWorkLoop.old.js

只要 workInProgress 不为空,则执行 performUnitOfWork:

js
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

8. performUnitOfWork ​

源码位置:packages/react-reconciler/src/ReactFiberWorkLoop.old.js

每个执行单元都有 beginWork 和 completeUnitOfWork 两个阶段:beginWork 负责将 vdom 转 fiber;completeUnitOfWork 负责处理 diff 结果。

js
function performUnitOfWork(unitOfWork: Fiber): void {
  // ...
  next = beginWork(current, unitOfWork, subtreeRenderLanes);

  if (next === null) {
    // 如果没有新的 work, 完成当前工作.
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}

9. beginWork ​

源码位置:packages/react-reconciler/src/ReactFiberBeginWork.old.js

负责将 vdom 转 fiber,不同节点的 reconcile 逻辑不同:函数组件调用 updateFunctionComponent;class 组件调用 updateClassComponent。

js
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  // ...
  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,
    );
  }
}

10. updateFunctionComponent ​

源码位置:packages/react-reconciler/src/ReactFiberBeginWork.old.js

函数组件会被调用,拿到 render 出的 vdom 继续进行 reconcile:

js
function updateFunctionComponent(
  current,
  workInProgress,
  Component,
  nextProps: any,
  renderLanes,
) {
  // 构建 hooks
  nextChildren = renderWithHooks(
    current,
    workInProgress,
    Component,
    nextProps,
    context,
    renderLanes,
  );
  // 生成 Fiber 链表
  reconcileChildren(current, workInProgress, nextChildren, renderLanes);
}

11. reconcileChildren ​

源码位置:packages/react-reconciler/src/ReactFiberBeginWork.old.js

判断 current 的值,如果是第一次更新则不进行 diff:

js
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderExpirationTime: ExpirationTime,
) {
  if (current === null) {
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderExpirationTime,
    );
  }
}

12. ChildReconciler ​

源码位置:packages/react-reconciler/src/ReactChildFiber.old.js

mountChildFibers 和 reconcileChildFibers 都是 ChildReconciler 方法,只是传参不同:

js
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);

ChildReconciler 函数内部定义了一系列函数,入口函数则是 reconcileChildFibers:

js
function ChildReconciler(shouldTrackSideEffects) {
  // ...
  function reconcileChildFibers() {
    // ...
  }
  return reconcileChildFibers;
}