跳到主要内容

懒加载

React 路由的加载核心目标是延迟加载非首屏所需的路由组件,减少初始化包体积,提升应用加载速度。

  • 核心思想 : 按需加载,只有当用户访问某个特定路由时,才去加载该页面对应的组件代码,而不是在应用启动时就加载所有的页面代码

一、基本实现步骤

实现 React 路由懒加载主要依赖于三个核心 API :

  • React.lazy() : 用于动态地创建一个“懒加载”的组件
  • import() : 返回一个 Promise , 该 Promise 在模块加载完成后解析为模块的内容。 React.lazy() 接受一个返回 import() 的函数作为参数
  • <Suspense> : 该组件用于包裹任何懒加载的组件。接受一个 callback 属性,定义了在目标组件加载完成前要显示的占位内容

1. 使用 React.lazy() 包装动态导入的组件

React.lazy() 接受一个返回动态 import() 的函数,将其包装为一个异步组件。动态 import()ES6 提案,会被打包工具(如 WebpackVite )识别为代码分割点,生成独立的 chunk 。

// 动态加载 Home 组件
const Home = React.lazy(() => import('./pages/Home'));
// 动态加载 About 组件
const About = React.lazy(() => import('./pages/About'));

2. 使用 <Suspense> 处理加载状态

<Suspense>React 提供的用于处理异步加载动态的组件,可指定加载过程中的占位内容( fallback

import { Suspense } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router';

function App() {
return (
<Router>
{/** 全局加载提示 */}
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
}

二、实现原理

  • 动态代码分割:动态 import() 语法会被打包工具(如 Webpack ) 解析为 require.ensure 或类似的异步加载逻辑,将目标组件及其依赖分割为独立的 chunk 文件(如 home.chunk.js )。初始状态时,这些 chunk 不会下载,只有在路由匹配时才会触发加载
  • React.lazy() 的异步组件封装React.lazy() 内部会将导入的组件转换为一个异步组件( AsyncComponent )。当组件首次渲染时, AsyncComponent 会触发 import() 加载 chunk ,并在加载完成后渲染成实际组件;加载过程中会触发 <Suspense>fallback
    • 当应用初始化时,浏览器只下载并执行主包(包含 App 组件、 Router 核心组件)
    • 此时,其他组件病没有被加载
    • 当用户点击到某路径(如 /about )时
      • React Router 匹配到 <Route path="/about" element={<About />} />
      • React 发现 About 是一个由 lazy 创建的组件
      • React 执行传给 lazy 的函数,即 import('./pages/About')
      • 这将触发一个网络请求,去加载之前生成的 About.chunk.js 文件
      • 浏览器下载并执行这个 chunk 文件, import() 返回的 Promise 得到解析
  • <Suspense> 的状态管理<Suspense> 通过监听子组件的加载状态(通过 React 的上下文机制),在子组件为加载完成时触发 fallback ,加载完成后渲染实际内容。若多个懒加载组件嵌套, <Suspense> 可统一处理它们的加载状态
    • React.lazy() 内部维护着一个状态机,监听 import() 返回的 Promise
    • Promise 解析成功(即组件加载完毕)之前, lazy 组件会“挂起”( suspend ),此时 <Suspense> 组件检测到其子组件处于“挂起”状态,便立即渲染 fallback 内容
    • 一旦 Promise 成功解析, lazy 组件更新其内部的状态,触发重新渲染,然后 <Suspense> 自动停止渲染 fallback ,转而渲染真正加载完成的组件

三、 React Router 各版本实现方法

信息

React Router 不同版本实现的主要差异在于 API 设计和路由配置方式,但实现懒加载的核心技术 ( React.lazy() + <Suspense> ) 是完全一致的,因为它是React 和构建工具提供的,而非 React Router

1. v5

  • 路由配置方式 : 基于 React 组件的 component 属性(传入组件类或函数)。
  • 懒加载实现 : 直接将 React.lazy() 包装的组件传入 component 属性,用 <Suspense> 包裹整个路由树
import { Route, Switch } from 'react-router-dom';
const Home = React.lazy(() => import('./pages/Home'));

function App() {
return (
<Suspense fallback={<Loading />}>
<Switch>
{/** 直接传入懒加载组件 */}
<Route path="/" component={Home} />
</Switch>
</Suspense>
);
}

2. v6

  • 路由配置方式 : 废弃 component 属性,改用 element 属性(传入 React 元素 )
  • 懒加载实现 : 需将 React.lazy() 包装的组件包裹在 <Suspense> 中,再作为 element 的值。路由树整体用 <Suspense> 包裹以处理所有的子路由的加载状态
  • 嵌套路由 : 嵌套的懒加载组件同样需要在父级 <Route>element 中用 <Suspense> 包裹,或全局包裹
import { Routes, Route } from 'react-router-dom';
const Home = React.lazy(() => import('./pages/Home'));

function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
{/** element 接收 React 元素 */}
<Route path="/" element={<Home />} />
</Routes>
</Suspense>
);
}

3. v7

  • 路由配置方式 : 延续 v6 的元素配置 ( element 属性),并优化了 API 设计(如 createBrowserRouter 替代 <BrowserRouter> 的部分功能)
  • 懒加载实现 : 与 v6 完全一致,仍通过 element 属性传递 <Suspense> 包裹的懒加载组件。 v7 更强调类型安全( TypeScript 支持) 和性能优化,但懒加载的核心逻辑无变化
import { createBrowserRouter, RouterProvider } from 'react-router';
const Home = React.lazy(() => import('./pages/Home'));

const router = createBrowserRouter([
{
path: '/',
element: (
<Suspense fallback={<Loading />}>
<Home />
</Suspense>
),
},
]);

function App() {
return <RouterProvider router={router} />;
}
框架模式
const router = createBrowserRouter({
path: '/admin',
lazy: {
Component: () => import('./AdminDashboard'),
loader: () => import('./admin.loader'),
action: () => import('./admin.action'),
},
});

新特性:

  • 模块级懒加载 : 支持同时懒加载组件、 loaderaction 等逻辑,避免主包臃肿
  • remix 对齐 : 统一数据获取( loader )、提交 ( action )和错误处理 ( ErrorElement ) 体验
  • 精细错误边界 : 每一个路由可自定义 ErrorElement ,局部错误不影响全局 UI
  • 性能优化 : 通过 shouldRevalidate 控制路由是狗重新请求数据,减少冗余加载

4. 总结

特性v5v6v7
路由配置属性component (传入组件)element (传入 React 元素同左
懒加载实现方式直接传入 React.lazy() 组件需将 React.lazy() 组件包裹在 <Suspense> 作为元素同左,支持更灵活的路由定义
路由树包裹<Suspense> 包裹 <Switch><Suspense> 包裹 <Routes><Suspense> 包裹根路由或具体路由元素
性能优化基础代码分割嵌套路由优化模块级别懒加载 + 缓存机制