懒加载
React 路由的加载核心目标是延迟加载非首屏所需的路由组件,减少初始化包体积,提升应用加载速度。
- 核心思想 : 按需加载,只有当用户访问某个特定路由时,才去加载该页面对应的组件代码,而不是在应用启动时就加载所有的页面代码
一、基本实现步骤
实现 React 路由懒加载主要依赖于三个核心 API :
React.lazy(): 用于动态地创建一个“懒加载”的组件- import() : 返回一个
Promise, 该Promise在模块加载完成后解析为模块的内容。React.lazy()接受一个返回import()的函数作为参数 <Suspense>: 该组件用于包裹任何懒加载的组件。接受一个callback属性,定义了在目标组件加载完成前要显示的占位内容
1. 使用 React.lazy() 包装动态导入的组件
React.lazy() 接受一个返回动态 import() 的函数,将其包装为一个异步组件。动态 import() 是 ES6 提案,会被打包工具(如 Webpack 、 Vite )识别为代码分割点,生成独立的 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得到解析
- React Router 匹配到
- 当应用初始化时,浏览器只下载并执行主包(包含
<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'),
},
});
新特性:
- 模块级懒加载 : 支持同时懒加载组件、
loader、action等逻辑,避免主包臃肿 - 与 remix 对齐 : 统一数据获取(
loader)、提交 (action)和错误处理 (ErrorElement) 体验 - 精细错误边界 : 每一个路由可自定义
ErrorElement,局部错误不影响全局 UI - 性能优化 : 通过
shouldRevalidate控制路由是狗重新请求数据,减少冗余加载
4. 总结
| 特性 | v5 | v6 | v7 |
|---|---|---|---|
| 路由配置属性 | component (传入组件) | element (传入 React 元素 | 同左 |
| 懒加载实现方式 | 直接传入 React.lazy() 组件 | 需将 React.lazy() 组件包裹在 <Suspense> 作为元素 | 同左,支持更灵活的路由定义 |
| 路由树包裹 | <Suspense> 包裹 <Switch> | <Suspense> 包裹 <Routes> | <Suspense> 包裹根路由或具体路由元素 |
| 性能优化 | 基础代码分割 | 嵌套路由优化 | 模块级别懒加载 + 缓存机制 |