- Published on
lazy 内部机制
React.lazy() 是 React 框架提供的一个内置 API,用于实现组件的代码分割 (Code Splitting)。它允许开发者在不改变组件编写方式的前提下,以一种声明式的方式,按需加载组件代码。这不仅可以显著减小应用初始加载的包体积,优化首次内容绘制 (FCP),也使得管理异步加载状态变得更加优雅。
lazy() 的核心机制
返回一个特殊的 React 元素
React.lazy() 并非返回一个普通的函数组件,而是返回一个特殊的、内部数据结构经过特殊处理的对象。
React.lazy() 的返回类型React.lazy() 返回一个带有 $$typeof: REACT_LAZY_TYPE 标识的特殊对象。这个对象不包含任何渲染逻辑,其内部的 _payload 属性包含了加载所需的一切信息,而 _init 方法则包含了实际的加载逻辑。
_payload: 存储了加载状态,其核心字段包括:_status: 标记加载状态,如0(未初始化)、1(加载中)、2(已解析)、3(已失败)。_result: 存储了加载的产物,如模块的Promise、解析后的组件或错误对象。
_init: 一个初始化方法。当它被调用时,会触发模块加载,并根据加载结果更新_payload中的_status和_result。
异步加载的内部状态管理
React.lazy() 的机制巧妙地将加载过程的状态管理与 React 的渲染流程结合在一起。
协调过程中的特殊处理
React.lazy() 的设计并非仅仅是为了方便调用 import()。它与 Fiber 架构的深度集成,旨在实现类型安全和性能优化。
LazyComponent 的 Fiber 节点
当 React 在渲染阶段遇到 lazy() 返回的特殊对象时,它会通过 createFiberFromTypeAndProps 将其映射为一个专用的、tag 类型为 LazyComponent 的 Fiber 节点。
mountLazyComponent 的工作流
LazyComponent 类型的 Fiber 节点,由 mountLazyComponent() 函数专门处理。其工作流程如下:
- 从 Fiber 节点的
elementType(即lazy()返回的对象)中,取出_payload和_init。 - 调用
_init(_payload)触发模块加载。- 如果模块正在加载中,
_init会抛出一个 Promise,React 捕获这个 Promise 并触发Suspense。 - 如果模块已加载,
_init会返回真实组件(例如,函数组件或类组件)。
- 如果模块正在加载中,
- 一旦获得真实组件,
mountLazyComponent会将 Fiber 节点的type更新为该组件,并通过resolveLazyComponentTag()函数判定其类型(例如FunctionComponent)。 - 最后,根据判定出的真实类型,调用相应的渲染逻辑(如
updateFunctionComponent),直接为真实组件创建其子 Fiber 节点。
这种“立即解析”的机制是原生 lazy() 的一项重要优化。一个自己封装的懒加载高阶组件,通常会创建一层额外的 Fiber 节点来管理状态,这增加了 Fiber 树的深度和渲染开销。而原生 lazy() 在 LazyComponent 节点内部完成了所有的状态管理和加载,然后直接将该 Fiber 节点“变身”为真实组件的 Fiber 节点,避免了这层冗余,使得 Fiber 树更加紧凑高效。
优化与类型安全的考量
类型安全:确保组件是有效类型
React.lazy() 的内部逻辑会通过 resolveLazyComponentTag() 确保加载的模块导出了一个有效的组件类型(如函数组件、类组件、React.memo 或 React.forwardRef 包装的组件)。如果模块导出的是一个非法的类型(例如原始值),React 会抛出一个清晰的错误,这增强了应用的健壮性。
memo() 和 forwardRef() 的特殊处理
React.lazy() 的内部机制能够识别出由 memo() 和 forwardRef() 包装的特殊组件类型,并为其分配专门的 Fiber.tag。这种处理使得原生 lazy() 能够与 React 的核心优化和特性无缝协作,避免了开发者需要自行处理这些复杂情况。