深入讲解 React.lazy 与 Suspense 的底层源码实现
一、历史背景与发展历程
1.1 引入动机:提升首屏加载性能
在 Web 应用日益复杂的背景下,一次性加载所有 JavaScript 资源会导致首屏加载缓慢。React 社区早期依赖第三方库(如 react-loadable
)来实现组件的异步加载。
1.2 官方方案诞生:v16.6 引入 React.lazy 和 Suspense
React 团队在 v16.6 中正式引入了:
React.lazy
:用于动态导入组件;<Suspense>
:用于捕获懒加载过程中的 Promise,并展示 loading 状态;
这一组合实现了官方原生支持的“按需加载”机制。
1.3 并发模式下的演进(v17+)
随着并发模式(Concurrent Mode)和 Fiber 架构的发展,React.lazy
也逐步支持中断与恢复机制,使得 React 在处理异步任务时更加灵活高效。
二、核心源码文件定位(React v18.2)
以下为 React 源码中与 React.lazy
和 Suspense
相关的核心文件路径:
文件名 | 路径 | 功能 |
---|---|---|
ReactLazy.js | packages/react/src/ReactLazy.js | 定义 lazy 组件结构和 resolve 函数 |
ReactFiberBeginWork.js | packages/react-reconciler/src/ReactFiberBeginWork.js | 处理 Fiber 架构下对 lazy 组件的解析 |
ReactFiberSuspend.js | packages/react-reconciler/src/ReactFiberSuspend.js | 控制 Suspense 行为逻辑 |
ReactFiberNewContext.js | packages/react-reconciler/src/ReactFiberNewContext.js | 上下文支持 |
三、React.lazy 实现原理详解
3.1 数据结构设计
// packages/react/src/ReactLazy.js
const REACT_LAZY_TYPE = Symbol.for('react.lazy');
function lazy(ctor) {
return {
$$typeof: REACT_LAZY_TYPE,
_payload: {
_status: -1, // -1: pending, 0: resolved, 1: rejected
_result: null, // 加载结果或错误
_ctor: ctor, // 动态导入函数
},
};
}
字段说明:
$$typeof
: React 内部标识对象类型的 Symbol。_payload._status
: 加载状态:-1
:未完成(pending)0
:已完成(resolved)1
:失败(rejected)
_payload._result
: 缓存加载后的模块(.default
)或错误信息。_payload._ctor
: 用户传入的动态导入函数,如() => import('./MyComponent')
3.2 resolveLazyComponent 核心函数
该函数在组件首次渲染时调用,负责实际执行导入函数并缓存结果。
function resolveLazyComponent(lazyComponent) {
// 获取懒加载组件的payload对象
const payload = lazyComponent._payload;
// 检查组件加载状态:0表示已加载完成
if (payload._status === 0) {
// 返回已加载组件的默认导出
return payload._result.default;
}
// 检查组件加载状态:1表示加载失败
if (payload._status === 1) {
// 抛出加载过程中捕获的错误
throw payload._result;
}
// 如果状态不是0或1,说明是第一次加载(初始状态)
const ctor = payload._ctor; // 获取组件加载函数(通常是import()调用)
try {
// 执行加载函数获取Promise
const modulePromise = ctor();
// 更新payload状态为加载中(-1)
payload._result = modulePromise;
payload._status = -1;
// 设置Promise的then/catch处理
modulePromise.then(
// 加载成功处理
result => {
payload._status = 0; // 更新状态为已完成
payload._result = result; // 存储加载结果
},
// 加载失败处理
error => {
payload._status = 1; // 更新状态为已失败
payload._result = error; // 存储错误对象
}
);
// 抛出Promise,让Suspense可以捕获并处理加载状态
throw modulePromise;
} catch (error) {
// 如果ctor()执行出错(同步错误),直接抛出
throw error;
}
}
注释说明:
- 缓存机制:一旦加载完成,将结果缓存到
_payload._result
中,避免重复加载。 - 异常传递:加载过程中抛出的是
Promise
,而不是真正的组件,这是为了让Suspense
能够捕获并展示 loading 状态。 - 异步流程控制:通过
.then()
更新加载状态,并最终返回组件。
四、Fiber 架构下的协作机制
4.1 beginWork 阶段识别 Lazy Component
这段代码来自React的Fiber架构实现,具体是在ReactFiberBeginWork.js
文件中。这是React内部处理组件渲染流程的核心部分之一。下面是老曹详细注释的代码:
// ReactFiberBeginWork.js
/**
* 开始处理Fiber节点的work(协调阶段)
* @param {FiberNode} current - 当前Fiber树中对应的节点(可能为null,如果是新创建的节点)
* @param {FiberNode} workInProgress - 正在处理的Fiber节点
* @param {Lanes} renderLanes - 表示当前渲染优先级的lane(s)
* @returns {?FiberNode} - 返回下一个需要处理的Fiber节点
*/
function beginWork(current, workInProgress, renderLanes) {
// 根据Fiber节点的tag类型,使用不同的更新策略
switch (workInProgress.tag) {
case LazyComponent: {
// 处理懒加载组件(通过React.lazy创建的组件)
// 调用updateLazyComponent来处理懒加载组件的更新逻辑
return updateLazyComponent(current, workInProgress, renderLanes);
}
// 这里省略了其他case处理...
// React内部有几十种不同的组件类型需要处理
}
}
关键点说明:
-
beginWork函数:这是React Fiber协调阶段的核心函数,负责处理每个Fiber节点的渲染工作。
-
参数说明:
current
:当前Fiber树中对应的节点,如果是新创建的节点则为nullworkInProgress
:正在处理的Fiber节点(属于workInProgress树)renderLanes
:表示当前渲染优先级的lane(s),用于调度优先级控制
-
LazyComponent处理:
- 当遇到
LazyComponent
类型的Fiber节点时(表示这是一个懒加载组件) - 调用
updateLazyComponent
函数来处理这类组件的更新逻辑 updateLazyComponent
会处理懒加载组件的加载状态和渲染
- 当遇到
-
Fiber架构:
- 这是React 16+引入的新协调算法
- 将渲染工作分解为增量单元(Fiber节点),支持时间切片和优先级调度
-
懒加载组件:
- 通过
React.lazy
创建的组件会在首次渲染时动态加载 - 通常与
Suspense
组件配合使用来显示加载状态
- 通过
4.2 updateLazyComponent 调用 resolveLazyComponent
这段代码来自React的Fiber架构实现,具体是在处理懒加载组件(LazyComponent
)的更新逻辑。下面是老曹详细注释的代码:
/**
* 更新懒加载组件
* @param {FiberNode} current - 当前Fiber树中对应的节点(可能为null,如果是新创建的节点)
* @param {FiberNode} workInProgress - 正在处理的Fiber节点
* @param {Lanes} renderLanes - 表示当前渲染优先级的lane(s)
* @returns {?FiberNode} - 返回下一个需要处理的Fiber节点
*/
function updateLazyComponent(current, workInProgress, renderLanes) {
// 解析懒加载组件的实际类型
// workInProgress.type 包含懒加载组件的描述信息
// resolveLazyComponentType 会返回已加载的组件或抛出Promise
const Component = resolveLazyComponentType(workInProgress.type);
// 后续处理:
// 现在 Component 是一个普通的函数组件或类组件
// React会将其视为普通组件继续处理
// 包括:
// 1. 克隆Fiber节点以应用新的类型
// 2. 设置正确的tag(可能是FunctionComponent或ClassComponent)
// 3. 继续协调子组件
// ...
}
其中 resolveLazyComponentType
是关键函数,内部调用了我们前面提到的 resolveLazyComponent
。
关键点说明:
-
updateLazyComponent函数:专门用于处理通过
React.lazy
创建的懒加载组件的更新逻辑。 -
参数说明:
current
:当前Fiber树中对应的节点,如果是新创建的节点则为nullworkInProgress
:正在处理的Fiber节点(属于workInProgress树)renderLanes
:表示当前渲染优先级的lane(s),用于调度优先级控制
-
resolveLazyComponentType:
- 这是一个内部函数,负责解析懒加载组件的实际类型
- 它会处理组件的加载状态(已加载、加载中、加载失败)
- 如果组件尚未加载,它会抛出Promise,让Suspense可以捕获并显示fallback UI
- 如果组件已加载,它返回实际的组件类型
-
后续处理:
- 一旦获取到实际的组件类型(
Component
),React会将其视为普通组件处理 - 这包括克隆Fiber节点、设置正确的组件类型tag(函数组件或类组件)
- 然后继续协调(reconcile)子组件
- 一旦获取到实际的组件类型(
-
与Suspense的协作:
- 懒加载组件通常与
Suspense
组件一起使用 - 当组件正在加载时,
Suspense
会显示其fallback内容 - 这种协作是通过
resolveLazyComponentType
抛出Promise实现的
- 懒加载组件通常与
五、Suspense 捕获 Promise 的机制
5.1 源码位置
这段代码来自React的Fiber架构实现,具体是在ReactFiberSuspend.js
文件中。这是一个非常简单的辅助函数,用于处理异步加载状态。下面是老曹详细注释的代码:
// ReactFiberSuspend.js
/**
* 如果传入的Promise仍然处于pending状态,则抛出该Promise
* @param {Promise} promise - 需要检查的Promise对象
* @throws {Promise} - 如果Promise仍处于pending状态,则抛出该Promise
*/
function throwIfStillPending(promise) {
// 简单直接地抛出传入的Promise
// 这个函数通常用于Suspense机制中
// 当React需要检查异步操作是否完成时
throw promise;
}
当组件抛出一个 Promise 时,React 会检查其是否被 <Suspense>
包裹,如果包裹,则显示 fallback UI;否则直接报错。
关键点说明:
-
函数目的:
- 这是一个辅助函数,用于在React的Suspense机制中处理异步加载状态
- 当React需要检查一个异步操作(如懒加载组件)是否完成时,会调用此函数
-
工作原理:
- 函数非常简单,只是直接抛出传入的Promise
- 这种抛出机制是Suspense能够捕获异步状态的关键
- 如果Promise仍处于pending状态,React的Suspense机制会捕获这个Promise并显示fallback UI
-
使用场景:
- 通常与
React.lazy
和Suspense
一起使用 - 当懒加载组件仍在加载时,这个函数会被用来中断当前渲染
- 允许React显示加载状态,直到Promise解决
- 通常与
-
错误处理:
- 这个函数本身不处理错误,只是抛出Promise
- 错误处理由调用者或React的Suspense机制负责
-
与React Fiber架构的关系:
- 这是React Fiber架构中处理异步渲染的一部分
- 允许React在等待异步操作时暂停渲染,并在操作完成后恢复
5.2 fallback 显示逻辑
这段代码是React内部实现的一部分,用于处理Suspense
组件的挂载阶段。实际的React实现要复杂得多。下面是老曹详细注释的简化代码:
// ReactFiberSuspend.js
/**
* 挂载Suspense组件
* @param {FiberNode} workInProgress - 正在处理的Fiber节点
* @param {Object} suspenseProps - Suspense组件的props
* @param {ReactNode} suspenseProps.children - Suspense包裹的子组件
* @param {ReactNode} suspenseProps.fallback - 加载中显示的内容
* @returns {ReactNode} - 返回要渲染的内容
*/
function mountSuspenseComponent(workInProgress, suspenseProps) {
const { children, fallback } = suspenseProps;
// 检查当前是否有挂起的Promise(即是否有异步加载正在进行)
if (isCurrentPromisePending()) {
// 如果有挂起的Promise,返回fallback内容(通常是加载指示器)
return fallback;
} else {
// 如果没有挂起的Promise,返回子组件内容
return children;
}
}
关键点说明:
-
函数目的:
- 处理
Suspense
组件的初始挂载 - 决定是显示加载状态(fallback)还是显示实际内容(children)
- 处理
-
参数说明:
workInProgress
:正在处理的Fiber节点suspenseProps
:Suspense组件的props对象children
:Suspense包裹的子组件fallback
:加载中显示的内容
-
isCurrentPromisePending:
- 这是一个内部函数,用于检查当前是否有未解决的Promise
- 在实际实现中,React会跟踪所有挂起的异步操作
- 这个函数检查是否有任何异步加载仍在进行中
-
行为逻辑:
- 如果有挂起的Promise(如懒加载组件正在加载),返回
fallback
内容 - 如果没有挂起的Promise,返回
children
内容
- 如果有挂起的Promise(如懒加载组件正在加载),返回
-
实际实现注意事项:
- 实际React实现要复杂得多,涉及Fiber架构的多个方面
- 需要处理边界情况、错误状态和并发渲染
- 会与调度器(Scheduler)和协调器(Reconciler)紧密协作
-
与React.lazy的关系:
- 通常与
React.lazy
创建的懒加载组件一起使用 - 提供优雅的加载状态显示机制
- 通常与
这个简化版本展示了Suspense的基本工作原理,但实际实现需要考虑更多细节和边界情况。
六、完整算法流程图解
+-----------------------------------+ +-------------------------------+
| 组件调用 React.lazy(() => import(...)) |---->| 创建 Lazy Component 对象 |
+-----------------------------------+ +-------------------------------+
|
v
+-------------------------------+
| 渲染组件时调用 |
| resolveLazyComponent() |
+-------------------------------+
|
v
+-------------------------------+
| 判断 _status 是否为 0? |
+-------------------------------+
|
+-----------+-----------+
| |
v v
+-----------------------+ +-----------------------+
| 是 - 直接返回缓存组件 | | 否 - 调用 ctor() 触发 |
+-----------------------+ | import() |
+-----------------------+
|
v
+-----------------------+
| 设置 _status 为 -1, |
| _result 为 Promise |
+-----------------------+
|
v
+-----------------------+
| 抛出 Promise |
+-----------------------+
|
v
+-----------------------+
| Suspense 捕获异常 |
+-----------------------+
|
v
+-----------------------+
| Promise 是否成功? |
+-----------------------+
|
+-----------+-----------+
| |
v v
+-----------------+ +-----------------+
| 成功 - _status = 0, | | 失败 - _status = 1, |
| _result = module.default | | _result = error |
+-----------------+ +-----------------+
| |
v v
+-----------------+ +-----------------+
| 下次渲染返回组件 | | ErrorBoundary 或 |
+-----------------+ | fallback 显示错误 |
+-----------------+
七、代码模拟实现(完整版)
7.1 模拟 React.lazy
const REACT_LAZY_TYPE = Symbol.for('react.lazy');
function lazy(ctor) {
return {
$$typeof: REACT_LAZY_TYPE,
_payload: {
_status: -1, // -1: pending, 0: resolved, 1: rejected
_result: null,
_ctor: ctor,
},
};
}
7.2 resolveLazyComponent 函数
/**
* 解析懒加载组件
* @param {Object} lazyComponent - 懒加载组件对象
* @returns {React.Component|Promise} - 返回已加载的组件或抛出Promise
*/
function resolveLazyComponent(lazyComponent) {
// 获取懒加载组件的payload对象
const payload = lazyComponent._payload;
// 检查组件加载状态:0表示已加载完成
if (payload._status === 0) {
// 返回已加载组件的默认导出
return payload._result.default;
}
// 检查组件加载状态:1表示加载失败
if (payload._status === 1) {
// 抛出加载过程中捕获的错误
throw payload._result;
}
// 如果状态不是0或1,说明是第一次加载(初始状态)
const ctor = payload._ctor; // 获取组件加载函数(通常是import()调用)
try {
// 执行加载函数获取Promise
const modulePromise = ctor();
// 更新payload状态为加载中(-1)
payload._result = modulePromise;
payload._status = -1;
// 设置Promise的then/catch处理
modulePromise.then(
// 加载成功处理
result => {
payload._status = 0; // 更新状态为已完成
payload._result = result; // 存储加载结果
},
// 加载失败处理
error => {
payload._status = 1; // 更新状态为已失败
payload._result = error; // 存储错误对象
}
);
// 抛出Promise,让Suspense可以捕获并处理加载状态
throw modulePromise;
} catch (error) {
// 如果ctor()执行出错(同步错误),直接抛出
throw error;
}
}
7.3 Suspense 模拟实现
function MySuspense({ children, fallback }) {
try {
return children;
} catch (e) {
if (e.status === -1) {
return <>{fallback}</>;
}
throw e;
}
}
7.4 使用示例
const LazyComponent = lazy(() => import('./components/LazyComponent'));
function App() {
return (
<MySuspense fallback="Loading...">
<LazyComponent />
</MySuspense>
);
}
八、总结
特性 | 描述 |
---|---|
React.lazy | 接收一个返回 Promise 的函数,封装成可被 React 渲染器识别的 Lazy Component |
Suspense | 捕获组件抛出的 Promise,并根据加载状态展示 fallback 或 children |
异常传递机制 | 利用 JavaScript 的异常机制驱动 UI 展示 |
缓存机制 | 加载完成后缓存组件,避免重复加载 |
与 Fiber 协作 | 支持中断与恢复,适用于并发模式 |
设计模式 | 工厂模式 + 状态模式 + 异常传递 + 单例缓存 |
九、结语
通过对 React.lazy
和 Suspense
的源码分析、算法流程、数据结构设计、以及完整的代码模拟实现,我们可以看到其背后是一套精妙的状态管理与异步控制机制。
掌握这套机制,不仅有助于你理解 React 的异步渲染模型,也能帮助你在构建大型应用时更好地进行性能优化与架构设计。
如果你希望进一步研究,老曹建议阅读官方源码中的如下部分:
ReactLazy.js
:了解 lazy 基础结构;ReactFiberSuspend.js
:学习 Suspense 如何处理 promise;ReactFiberBeginWork.js
:观察组件如何在 Fiber 架构下解析 lazy 组件。