Skip to content

React 应用启动流程

React 应用的生命周期始于其初始化过程。无论是已经被删除的ReactDOM.hydrateReactDOM.render, 还是现代的 createRoothydrateRoot API,它们都扮演着将 React 组件树"挂载"到真实 DOM 上的关键角色。本章将深入探讨 React 应用的启动流程,从用户调用 API 到内部 Fiber 树的构建,以及初次渲染的概览。

入口函数

createRoot

在 React 17 之前,我们通常使用 ReactDOM.render 来启动一个 React 应用:

jsx
import ReactDOM from 'react-dom';
import App from './App';

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

而在 React 18 引入并发特性后,推荐使用 createRoot,甚至在react19中,将 ReactDOM.render 给移除。

jsx
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);

无论使用哪种方式,它们都是 React 应用的“入口点”。当这些方法被调用时,React 会开始一系列复杂的内部操作,将我们用 JSX 编写的组件描述转换为浏览器能够理解和渲染的实际 DOM 结构。这个过程的起点,就是将传入的 React Element(例如 <App />)转化为内部的 Fiber 树结构。

createRoot 函数是 React 18 及以后版本中创建应用根节点的标准 API,其主要职责是初始化 React 应用的运行环境并返回一个可用于渲染和卸载组件的 ReactDOMRoot 对象。

主要做的事情:

  1. 接收容器: 它接收一个真实的 DOM 元素作为参数,这个 DOM 元素就是你的 React 应用将会被挂载的地方(比如 document.getElementById('root'))。
  2. 创建 React 根 (FiberRoot):
    • 它会在内部创建一个 FiberRootNode 对象。你可以把 FiberRootNode 想象成整个 React 应用的“大脑”或“总控制室”,它存储了应用的顶层状态、待处理的更新、当前的 Fiber 树等核心信息。
    • 同时,它会创建一个特殊的 Fiber 节点,叫做 HostRootFiber。这个 HostRootFiber 是你的组件树在 Fiber 架构中的最顶层节点。
    • FiberRootNodeHostRootFiber 会相互引用,建立起 React 内部管理结构的基础。
  3. 启用并发特性: createRoot 创建的根默认支持 React 的并发特性 (Concurrent Mode),这意味着 React 可以更智能地安排渲染工作,提高应用的响应性。
  4. 设置事件系统: 它会为指定的 DOM 容器初始化 React 的事件系统,确保组件中的事件处理器能够正常工作,这个将在后续的章节详细讲解。
  5. 返回 Root 对象: createRoot 会返回一个 ReactDOMRoot 对象。这个对象上有一个重要的方法:
    • root.render(<App />): 你可以使用这个方法来首次渲染你的 React 组件到 DOM 容器中,或者在之后进行更新。
    • root.unmount(): 用于卸载整个 React 应用。

简单来说,createRoot 就是告诉 React:“嘿,在这个 DOM 节点里,给我创建一个全新的、支持并发渲染的 React 应用吧!”

主要区别总结:

特性createRoothydrateRoot
使用场景客户端纯渲染 (CSR)服务端渲染后的客户端激活 (SSR Hydration)
DOM 容器初始状态通常为空,或不包含 React 渲染的内容包含由服务端预渲染的 HTML 结构
DOM 操作创建新的 DOM 节点来构建 UI尝试复用已有的 DOM 节点,并附加事件监听器
核心目标从无到有构建和渲染 UI使服务端静态 HTML 变得可交互,并与客户端 React 状态同步
内部标记hydrate 标记为 false (默认)hydrate 标记为 true
createRoot内部实现
javascript
function createRoot(container, options) {
  // 1. 验证容器是否有效
  if (!isValidContainer(container)) {
    throw new Error('Target container is not a DOM element.');
  }

  // 2. 初始化选项 (options)
  // 3. 创建 FiberRootNode (核心)
  const root = createContainer(
    container,          // DOM 容器节点
    ConcurrentRoot,     // 根节点类型 (并发模式)
    null,               // hydrationCallbacks (非 hydrate 模式)
    isStrictMode,       // 是否严格模式
    false,              // concurrentUpdatesByDefaultOverride
    identifierPrefix,   // 标识符前缀
    onUncaughtError,    // 未捕获错误处理
    onCaughtError,      // 已捕获错误处理 (来自错误边界)
    onRecoverableError, // 可恢复错误处理
    transitionCallbacks,// 过渡回调
    null                // formState
  );
  // 4. 标记 DOM 容器,建立与 FiberRoot 的关联
  markContainerAsRoot(root.current, container);
  // 5. 在容器上监听所有支持的事件
  listenToAllSupportedEvents(container);
  // 6. 返回一个 ReactDOMRoot 实例
  return new ReactDOMRoot(root);
}

hydrateRoot

在 React 19 中,hydrateRoot 方法用于将已经存在的 DOM 结构与 React 组件进行“调和”,以确保它们保持同步。这在服务端渲染(SSR)或使用第三方库时非常有用,因为它允许 React 应用在客户端接管已经存在的 DOM 结构。

jsx
import { hydrateRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = hydrateRoot(domNode, <App />);

hydrateRoot 的关键在于它不仅创建了 React 应用的根,还立即启动了一个特殊的“hydration”(水合)渲染过程,旨在重用服务端渲染的 HTML,从而优化首屏加载体验。

在其内部函数中,主要做的事情:

  1. 接收容器和初始内容:
    • 它也接收一个 DOM 元素作为容器,但与 createRoot 不同的是,这个容器期望已经包含了由服务端预先渲染好的 HTML 结构
    • 它还接收一个 initialChildren 参数,这通常是你应用在服务端渲染时使用的顶级 React 元素 (例如 <App />)。
  2. 创建 React 根 (FiberRoot): 这一点与 createRoot 类似,它也会创建 FiberRootNodeHostRootFiber,为客户端的 React 运行建立基础。
  3. “激活”服务端 HTML (Hydration): 这是 hydrateRoot 最核心的步骤。
    • React 不会重新创建 DOM 节点,而是会尝试复用容器中已经存在的、由服务端渲染的 HTML 结构。
    • 它会遍历服务端生成的 HTML 和你在客户端提供的 initialChildren (React 元素树),试图将两者匹配起来。
    • 在这个过程中,它会给现有的 DOM 节点附加事件监听器,使得这些静态的 HTML 变得可交互。
    • 如果客户端的 React 树结构与服务端的 HTML 结构不完全匹配,React 会尝试进行修复,但可能会在控制台给出警告。严重不匹配可能导致 hydration 失败或行为异常。
  4. 设置 hydrate 标记: 在内部,它会将 FiberRootNode 上的一个 hydrate 标志设置为 true,告知后续的渲染流程当前处于 hydration 模式。
  5. 返回 Root 对象:createRoot 类似,它也会返回一个具备 render (用于后续更新) 和 unmount 方法的 Root 对象 (通常是 ReactDOMHydrationRoot 实例,其接口与 ReactDOMRoot 基本一致)。

简单来说,hydrateRoot 就是告诉 React:“看,这里有一些服务端已经渲染好的 HTML,请你接管它们,让它们在客户端‘活’起来,并且保持与我提供的 React 组件树一致。”

hydrateRoot内部实现
javascript
function hydrateRoot(container, initialChildren, options) {
  // 1. 验证容器是否有效
  if (!isValidContainer(container)) {
    throw new Error('Target container is not a DOM element.');
  }
  // 2. 初始化选项 (options)
  // 3. 创建用于 Hydration 的 FiberRootNode (核心)
  const root = createHydrationContainer(
    initialChildren,    // 初始的 React 元素 (通常是 <App />)
    null,               // parentComponent (通常为 null)
    container,          // DOM 容器节点 (期望包含服务端渲染的 HTML)
    ConcurrentRoot,     // 根节点类型 (并发模式)
    hydrationCallbacks, // Hydration 相关的回调 (如 onHydrated, onDeleted)
    isStrictMode,       // 是否严格模式
    false,              // concurrentUpdatesByDefaultOverride
    identifierPrefix,   // 标识符前缀
    onUncaughtError,    // 未捕获错误处理
    onCaughtError,      // 已捕获错误处理
    onRecoverableError, // 可恢复错误处理
    onDefaultTransitionIndicator, // 默认过渡指示器回调
    transitionCallbacks,// 过渡回调
    formState           // 表单状态 (用于服务端组件)
  );
  // 4. 标记 DOM 容器,建立与 FiberRoot 的关联
  markContainerAsRoot(root.current, container);
  // 5. 在容器上监听所有支持的事件
  listenToAllSupportedEvents(container);
  // 6. 返回一个 ReactDOMHydrationRoot 实例
  return new ReactDOMHydrationRoot(root);
}

createHydrationContainer

hydrateRoot内部中会调用createHydrationContainer函数,而 createHydrationContainer函数的主要目标是建立一个配置为水合模式的 FiberRootNode ,并立即调度一个特殊的“水合更新”。这个更新将启动 React 的协调过程,但与常规渲染不同,它会尝试将 initialChildren 与 containerInfo 中已存在的 DOM 结构进行匹配和复用,而不是完全重新创建 DOM。

createHydrationContainer 内部函数中,主要的步骤包括:

  • requestUpdateLane(current) : 获取一个用于本次更新的优先级“赛道”(Lane)。React 的并发调度模型使用 Lanes 来管理不同优先级的更新。
  • enableHydrationLaneScheduling : 这是一个特性开关,如果启用,可能会对水合的 Lane 进行特殊调整 (例如,通过 getBumpedLaneForHydrationByLane 提升其优先级或赋予特定属性),以确保水合过程能尽快或以特定方式执行。
  • createUpdate(lane) : 创建一个更新对象。对于初始水合,这个更新对象通常不携带 payload (即没有新的 element 数据),因为其目的是触发对 initialChildren (已在 FiberRootNode 中设置) 的水合处理,而不是渲染新的内容。
  • update.callback : 如果用户在调用 hydrateRoot 时提供了回调函数,这个回调会被关联到更新对象上,并在水合操作完成后执行。
  • enqueueUpdate(current, update, lane) : 将创建的更新对象添加到 HostRoot Fiber 的更新队列中。这使得该更新成为待处理状态。
  • scheduleInitialHydrationOnRoot(root, lane) : 这是触发实际水合工作调度的关键函数。它会确保 React 的调度器 (Scheduler) 知道有一个水合任务需要在指定的 lane 上执行。这个函数内部会进一步调用调度相关的 API (如 ensureRootIsScheduled ) 来安排工作循环的启动。
createHydrationContainer内部实现
javascript
// ... existing code ...
export function createHydrationContainer(
  initialChildren: ReactNodeList, // 初始的 React 子节点 (期望与服务端渲染的 HTML 结构对应)
  callback: ?Function, // 可选的回调函数,在水合完成后执行 (主要用于遗留模式)
  containerInfo: Container, // 宿主环境的容器节点 (例如 DOM 元素)
  tag: RootTag, // Root 的类型 (通常是 ConcurrentRoot)
  hydrationCallbacks: null | SuspenseHydrationCallbacks, // Hydration 相关的回调 (例如 onHydrated)
  isStrictMode: boolean, // 是否启用严格模式
  concurrentUpdatesByDefaultOverride: null | boolean, // 已被忽略的参数
  identifierPrefix: string, // 用于生成唯一 ID 的前缀
  onUncaughtError: (error: mixed, errorInfo: {+componentStack?: ?string}) => void, // 未捕获错误的回调
  onCaughtError: (error: mixed, errorInfo: {/* ... */}) => void, // 已捕获错误的回调
  onRecoverableError: (error: mixed, errorInfo: {/* ... */}) => void, // 可恢复错误的回调
  onDefaultTransitionIndicator: () => void | (() => void), // Transition 默认指示器的回调
  transitionCallbacks: null | TransitionTracingCallbacks, // Transition 追踪相关的回调
  formState: ReactFormState<any, any> | null, // 表单状态
): OpaqueRoot {
  // 1. 设置 hydrate 标志为 true
  const hydrate = true;
  // 2. 调用 createFiberRoot 创建 FiberRootNode
  const root = createFiberRoot(
    containerInfo,
    tag,
    hydrate, // <--- 关键:标记为水合模式
    initialChildren,
    hydrationCallbacks,
    isStrictMode,
    identifierPrefix,
    formState,
    onUncaughtError,
    onCaughtError,
    onRecoverableError,
    onDefaultTransitionIndicator,
    transitionCallbacks,
  );
  // 注册 Transition 默认指示器回调
  registerDefaultIndicator(onDefaultTransitionIndicator);
  // 3. 设置根节点的 context (TODO: 官方注释提到未来可能移到 FiberRoot 构造函数中)
  root.context = getContextForSubtree(null);
  // 4. 调度初始的水合更新
  const current = root.current; // 获取 HostRoot Fiber
  let lane = requestUpdateLane(current); // 为此次更新请求一个合适的 Lane
  // 如果启用了特定的水合 Lane 调度策略,则调整 Lane
  if (enableHydrationLaneScheduling) {
    lane = getBumpedLaneForHydrationByLane(lane);
  }
  // 创建一个更新对象 (Update)
  // 注意:这个初始水合的 update 对象没有 payload,它的主要目的是触发调度。
  const update = createUpdate(lane);
  // 如果提供了回调函数,则将其附加到 update 对象上
  update.callback =
    callback !== undefined && callback !== null ? callback : null;
  // 将更新对象入队到 HostRoot Fiber 的更新队列中
  enqueueUpdate(current, update, lane);
  // 在根节点上调度初始的水合过程
  scheduleInitialHydrationOnRoot(root, lane);
  // 5. 返回创建和配置好的 FiberRootNode (OpaqueRoot 类型)
  return root;
}
// ... existing code ...

核心函数概念

createFiberRoot

createRoothydrateRoot 内部,都会分别调用核心函数 createContainercreateHydrationContainer,它负责创建 FiberRootNode 实例。但在函数内部,都会调用一个核心函数 createFiberRoot。 在其内部的核心流程如下:

  1. 创建 FiberRootNode 实例 (new FiberRootNode(...)):

    • 这是整个 React 应用的“大脑”或“中枢系统”。它不是一个 Fiber 节点,而是一个更高级别的对象,用于管理整个 Fiber 树的状态和更新。
    • 它存储了指向当前生效的 Fiber 树 (current 指针)、容器信息 (containerInfo)、各种配置(如 taghydrate)、错误处理回调 (onUncaughtError, onCaughtError, onRecoverableError)、以及与并发特性相关的上下文(如 identifierPrefix, formState, transitionCallbacks)。
  2. 创建 HostRoot Fiber 节点:

  3. 建立双向链接:

    • root.current = uninitializedFiber;: FiberRootNodecurrent 属性指向当前活动(或即将成为活动)的 Fiber 树的根 Fiber 节点。在初始化时,这就是我们刚创建的 uninitializedFiber
    • uninitializedFiber.stateNode = root;: 相反地,HostRoot Fiber 节点的 stateNode 属性指向它所属的 FiberRootNode。这种双向链接使得在遍历 Fiber 树时可以方便地访问到 FiberRootNode,反之亦然。
  4. 初始化缓存 (createCache(), retainCache()):

    • React 引入了 Cache API 用于在 Suspense 等场景下缓存数据。
    • root.pooledCache 是一个特殊的缓存,用于在渲染过程中为新挂载的组件(特别是 Suspense 边界)提供临时的缓存。它在每次渲染提交后会被清理或转移。
  5. 初始化 HostRoot Fiber 的 memoizedState:

    • memoizedState 存储了 Fiber 节点计算后的状态。
    • 对于 HostRoot Fiber,其初始 memoizedState 是一个对象,包含:
      • element: 即 initialChildren,这是 React 应用的初始 JSX 内容,将作为首次渲染的输入。
      • isDehydrated: 一个布尔值,标记当前根是否处于 “脱水” 状态,这主要用于 hydrateRoot 流程,指示 React 需要尝试复用服务端渲染的 HTML。
      • cache: 指向上面创建的 initialCache
  6. 初始化 HostRoot Fiber 的更新队列 (initializeUpdateQueue(uninitializedFiber)):

    • 更新队列负责存储和处理对该 Fiber 节点状态的更新请求(例如,通过 setStateforceUpdate 触发的更新)。对于 HostRoot Fiber,初始的 render 调用(如 root.render(<App />))也会通过这个更新队列来调度。

createFiberRoot 函数是 React 应用启动过程中的奠基石。它构建了应用状态管理的核心 (FiberRootNode) 和 UI 结构的起点 (HostRoot Fiber),并为后续的渲染和更新流程做好了准备。

createFiberRoot 内部实现
javascript
// ... existing code ...
export function createFiberRoot(
  containerInfo: Container, // 宿主环境的容器节点 (例如 DOM 元素)
  tag: RootTag, // Root 的类型 (LegacyRoot, ConcurrentRoot)
  hydrate: boolean, // 是否是 Hydration 模式
  initialChildren: ReactNodeList, // 初始的 React 子节点
  hydrationCallbacks: null | SuspenseHydrationCallbacks, // Hydration 相关的回调
  isStrictMode: boolean, // 是否启用严格模式
  identifierPrefix: string, // 用于生成唯一 ID 的前缀
  formState: ReactFormState<any, any> | null, // 表单状态,与 React DOM for form actions 相关
  onUncaughtError: (error: mixed, errorInfo: {+componentStack?: ?string}) => void, // 未捕获错误的回调
  onCaughtError: (error: mixed, errorInfo: {/* ... */}) => void, // 已捕获错误的回调 (通常由错误边界处理)
  onRecoverableError: (error: mixed, errorInfo: {/* ... */}) => void, // 可恢复错误的回调
  onDefaultTransitionIndicator: () => void | (() => void), // Transition 默认指示器的回调
  transitionCallbacks: null | TransitionTracingCallbacks, // Transition 追踪相关的回调
): FiberRoot {
  // 1. 创建 FiberRootNode 实例
  const root: FiberRoot = new FiberRootNode(
    containerInfo,
    tag,
    hydrate,
    identifierPrefix,
    onUncaughtError,
    onCaughtError,
    onRecoverableError,
    onDefaultTransitionIndicator,
    formState,
  );
  // 如果启用了 Suspense 回调,则设置 hydrationCallbacks
  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }
  // 如果启用了 Transition 追踪,则设置 transitionCallbacks
  if (enableTransitionTracing) {
    root.transitionCallbacks = transitionCallbacks;
  }
  // 2. 创建 HostRoot Fiber 节点
  const uninitializedFiber = createHostRootFiber(tag, isStrictMode);
  // 3. 建立 FiberRootNode 和 HostRoot Fiber 之间的双向链接
  root.current = uninitializedFiber; // FiberRootNode 的 current 指针指向 HostRoot Fiber
  uninitializedFiber.stateNode = root; // HostRoot Fiber 的 stateNode 指向 FiberRootNode
  // 4. 初始化缓存 (Cache)
  // 用于 Suspense 和其他需要缓存数据的特性
  const initialCache = createCache();
  retainCache(initialCache); // 增加缓存的引用计数
  // pooledCache 用于渲染过程中新挂载的边界,在渲染结束后会被清理或转移
  root.pooledCache = initialCache;
  retainCache(initialCache);
  // 5. 初始化 HostRoot Fiber 的 memoizedState
  const initialState: RootState = {
    element: initialChildren,
    isDehydrated: hydrate, // 标记是否处于 dehydration 状态 (用于 hydrateRoot)
    cache: initialCache,
  };
  uninitializedFiber.memoizedState = initialState;
  // 6. 初始化 HostRoot Fiber 的更新队列
  initializeUpdateQueue(uninitializedFiber);
  // 7. 返回创建好的 FiberRootNode
  return root;
}
// ... existing code ...

render

render 方法是 createRoothydrateRoot 返回的 RootType 对象上的核心 API 之一。它用于告诉 React 需要在根容器中渲染或更新哪些 React元素。

这一步的细节将在协调阶段上进行详细的讲解。

unmount

unmount 方法是 createRoothydrateRoot 返回的 RootType 对象上的核心 API 之一,调用 root.unmount 以销毁 React 根节点中的一个已经渲染的树。

在其内部主要做了两件事情:

  • 调用 updateContainerSync 同步执行,触发实际卸载流程,其会通知Reconciler,让Reconciler从根节点开始,自顶向下遍历整个 Fiber 树:
    • 对于每个组件,会执行其卸载相关的生命周期方法(例如,类组件的 componentWillUnmount ,函数组件中 useEffect 的清理函数)。
    • 移除所有关联的 DOM 节点。
    • 清理事件监听器等资源。
  • 调用 flushSyncWork 确保同步工作完成:
    • 立即执行当前所有挂起的同步优先级的任务。
    • 在 unmount 的上下文中,这确保了由 updateContainerSync(null, ...) 触发的卸载操作(包括生命周期方法的执行和 DOM 的移除)被立即、同步地完成,而不是被推迟到未来的某个时间点。
unmount
javascript
// ... existing code ...
ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount =
  function (): void {
    const root = this._internalRoot; // 获取内部的 FiberRootNode
    if (root !== null) { // 检查根是否已经被卸载
      this._internalRoot = null; // 将内部根引用置为 null,标记为已卸载
      const container = root.containerInfo; // 获取 DOM 容器节点
      // 1. 同步地更新容器内容为 null,这将触发组件的卸载生命周期
      // updateContainerSync(null, root, null, null);
      updateContainerSync(null, root, null, null);
      // 2. 确保所有同步工作完成
      flushSyncWork();

      // 3. 从 DOM 容器上移除 React 根标记
      unmarkContainerAsRoot(container);
    }
  };
// ... existing code ...

unstable_scheduleHydration

unstable_scheduleHydrationReactDOMHydrationRoot 原型上的一个不稳定方法,它实际上是内部函数 scheduleHydration 的别名。这个方法允许你显式地调度一个 DOM 节点的 hydration。

此方法主要用于更细粒度地控制 hydration 过程。通常,当你调用 hydrateRoot 时,React 会尝试 hydration 整个提供的容器。 unstable_scheduleHydration 允许开发者(或 React 内部机制)在初始 hydration 之后,或者对于某些特定情况,指定某个 DOM 节点应该被 hydration。

例如,如果你的页面有一部分内容是SSR的,但另一部分内容(比如一个评论区)是稍后通过客户端请求获取HTML片段并插入到页面中的,你可能希望在插入这个新的HTML片段后,显式地告诉React去 hydrate 这个新片段,而不是重新渲染整个应用或依赖于复杂的父组件状态来触发。

unstable_scheduleHydration 提供了一个底层的、不稳定的接口,用于将特定的 DOM 节点加入队列,以便 React 在后续的渲染周期中尝试对其进行 hydration。这对于处理复杂的、逐步 hydration 的场景可能非常有用。

FiberRoot

在 React 内部,FiberRoot 是一个至关重要的概念。它不是一个 Fiber 节点,而是一个独立的 JavaScript 对象,用于管理整个 React 应用的根节点。每个通过 ReactDOM.rendercreateRoot 挂载的 React 应用都会有一个对应的 FiberRoot

FiberRoot 的主要作用:

  • 指向根 Fiber 节点: FiberRootcurrent 属性始终指向当前渲染在屏幕上的 HostRootFiber(即整个 Fiber 树的根节点)。
  • 管理更新队列: 它包含了所有待处理的更新(pendingLanes),以及与调度相关的优先级信息。
  • 存储工作中的 Fiber 树: 在协调阶段,FiberRoot 会有一个指向正在构建的 workInProgress 树的引用。
  • 错误边界: 它是错误边界捕获错误的起点。
  • 上下文: 存储一些全局上下文信息。

HostRootFiberFiberRoot 所指向的那个特殊的 Fiber 节点。它的 stateNode 属性指向真实的 DOM 元素,例如 <div id="root"></div>HostRootFiber 是整个 React 组件树的入口,所有其他组件的 Fiber 节点都是它的子孙。

它们之间的关系可以概括为:

FiberRoot 是一个管理对象,它“拥有”一个 HostRootFiberHostRootFiber 则是 Fiber 树的实际根节点,它代表了 React 应用在真实 DOM 中的挂载点。

流程图

为了让大家能更快理解整个流程,我将整个入口的流程图给大家展示一下,


微信公众号二维码