Appearance
React 应用启动流程
React 应用的生命周期始于其初始化过程。无论是已经被删除的ReactDOM.hydrate、 ReactDOM.render, 还是现代的 createRoot、hydrateRoot 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 对象。
主要做的事情:
- 接收容器: 它接收一个真实的 DOM 元素作为参数,这个 DOM 元素就是你的 React 应用将会被挂载的地方(比如
document.getElementById('root'))。 - 创建 React 根 (FiberRoot):
- 它会在内部创建一个
FiberRootNode对象。你可以把FiberRootNode想象成整个 React 应用的“大脑”或“总控制室”,它存储了应用的顶层状态、待处理的更新、当前的 Fiber 树等核心信息。 - 同时,它会创建一个特殊的 Fiber 节点,叫做
HostRootFiber。这个HostRootFiber是你的组件树在 Fiber 架构中的最顶层节点。 FiberRootNode和HostRootFiber会相互引用,建立起 React 内部管理结构的基础。
- 它会在内部创建一个
- 启用并发特性:
createRoot创建的根默认支持 React 的并发特性 (Concurrent Mode),这意味着 React 可以更智能地安排渲染工作,提高应用的响应性。 - 设置事件系统: 它会为指定的 DOM 容器初始化 React 的事件系统,确保组件中的事件处理器能够正常工作,这个将在后续的章节详细讲解。
- 返回 Root 对象:
createRoot会返回一个ReactDOMRoot对象。这个对象上有一个重要的方法:root.render(<App />): 你可以使用这个方法来首次渲染你的 React 组件到 DOM 容器中,或者在之后进行更新。root.unmount(): 用于卸载整个 React 应用。
简单来说,createRoot 就是告诉 React:“嘿,在这个 DOM 节点里,给我创建一个全新的、支持并发渲染的 React 应用吧!”
主要区别总结:
| 特性 | createRoot | hydrateRoot |
|---|---|---|
| 使用场景 | 客户端纯渲染 (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,从而优化首屏加载体验。
在其内部函数中,主要做的事情:
- 接收容器和初始内容:
- 它也接收一个 DOM 元素作为容器,但与
createRoot不同的是,这个容器期望已经包含了由服务端预先渲染好的 HTML 结构。 - 它还接收一个
initialChildren参数,这通常是你应用在服务端渲染时使用的顶级 React 元素 (例如<App />)。
- 它也接收一个 DOM 元素作为容器,但与
- 创建 React 根 (FiberRoot): 这一点与
createRoot类似,它也会创建FiberRootNode和HostRootFiber,为客户端的 React 运行建立基础。 - “激活”服务端 HTML (Hydration): 这是
hydrateRoot最核心的步骤。- React 不会重新创建 DOM 节点,而是会尝试复用容器中已经存在的、由服务端渲染的 HTML 结构。
- 它会遍历服务端生成的 HTML 和你在客户端提供的
initialChildren(React 元素树),试图将两者匹配起来。 - 在这个过程中,它会给现有的 DOM 节点附加事件监听器,使得这些静态的 HTML 变得可交互。
- 如果客户端的 React 树结构与服务端的 HTML 结构不完全匹配,React 会尝试进行修复,但可能会在控制台给出警告。严重不匹配可能导致 hydration 失败或行为异常。
- 设置
hydrate标记: 在内部,它会将FiberRootNode上的一个hydrate标志设置为true,告知后续的渲染流程当前处于 hydration 模式。 - 返回 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
在 createRoot 和 hydrateRoot 内部,都会分别调用核心函数 createContainer、createHydrationContainer,它负责创建 FiberRootNode 实例。但在函数内部,都会调用一个核心函数 createFiberRoot。 在其内部的核心流程如下:
创建
FiberRootNode实例 (new FiberRootNode(...)):- 这是整个 React 应用的“大脑”或“中枢系统”。它不是一个 Fiber 节点,而是一个更高级别的对象,用于管理整个 Fiber 树的状态和更新。
- 它存储了指向当前生效的 Fiber 树 (
current指针)、容器信息 (containerInfo)、各种配置(如tag、hydrate)、错误处理回调 (onUncaughtError,onCaughtError,onRecoverableError)、以及与并发特性相关的上下文(如identifierPrefix,formState,transitionCallbacks)。
创建 HostRoot Fiber 节点:
建立双向链接:
root.current = uninitializedFiber;:FiberRootNode的current属性指向当前活动(或即将成为活动)的 Fiber 树的根 Fiber 节点。在初始化时,这就是我们刚创建的uninitializedFiber。uninitializedFiber.stateNode = root;: 相反地,HostRoot Fiber 节点的stateNode属性指向它所属的FiberRootNode。这种双向链接使得在遍历 Fiber 树时可以方便地访问到FiberRootNode,反之亦然。
初始化缓存 (
createCache(),retainCache()):- React 引入了 Cache API 用于在 Suspense 等场景下缓存数据。
root.pooledCache是一个特殊的缓存,用于在渲染过程中为新挂载的组件(特别是 Suspense 边界)提供临时的缓存。它在每次渲染提交后会被清理或转移。
初始化 HostRoot Fiber 的
memoizedState:memoizedState存储了 Fiber 节点计算后的状态。- 对于 HostRoot Fiber,其初始
memoizedState是一个对象,包含:element: 即initialChildren,这是 React 应用的初始 JSX 内容,将作为首次渲染的输入。isDehydrated: 一个布尔值,标记当前根是否处于 “脱水” 状态,这主要用于hydrateRoot流程,指示 React 需要尝试复用服务端渲染的 HTML。cache: 指向上面创建的initialCache。
初始化 HostRoot Fiber 的更新队列 (
initializeUpdateQueue(uninitializedFiber)):- 更新队列负责存储和处理对该 Fiber 节点状态的更新请求(例如,通过
setState或forceUpdate触发的更新)。对于 HostRoot Fiber,初始的render调用(如root.render(<App />))也会通过这个更新队列来调度。
- 更新队列负责存储和处理对该 Fiber 节点状态的更新请求(例如,通过
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 方法是 createRoot 或 hydrateRoot 返回的 RootType 对象上的核心 API 之一。它用于告诉 React 需要在根容器中渲染或更新哪些 React元素。
这一步的细节将在协调阶段上进行详细的讲解。
unmount
unmount 方法是 createRoot 或 hydrateRoot 返回的 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_scheduleHydration是 ReactDOMHydrationRoot 原型上的一个不稳定方法,它实际上是内部函数 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.render 或 createRoot 挂载的 React 应用都会有一个对应的 FiberRoot。
FiberRoot 的主要作用:
- 指向根 Fiber 节点:
FiberRoot的current属性始终指向当前渲染在屏幕上的HostRootFiber(即整个 Fiber 树的根节点)。 - 管理更新队列: 它包含了所有待处理的更新(
pendingLanes),以及与调度相关的优先级信息。 - 存储工作中的 Fiber 树: 在协调阶段,
FiberRoot会有一个指向正在构建的workInProgress树的引用。 - 错误边界: 它是错误边界捕获错误的起点。
- 上下文: 存储一些全局上下文信息。
HostRootFiber 是 FiberRoot 所指向的那个特殊的 Fiber 节点。它的 stateNode 属性指向真实的 DOM 元素,例如 <div id="root"></div>。HostRootFiber 是整个 React 组件树的入口,所有其他组件的 Fiber 节点都是它的子孙。
它们之间的关系可以概括为:
FiberRoot 是一个管理对象,它“拥有”一个 HostRootFiber。HostRootFiber 则是 Fiber 树的实际根节点,它代表了 React 应用在真实 DOM 中的挂载点。
流程图
为了让大家能更快理解整个流程,我将整个入口的流程图给大家展示一下,
