1. 基本概念
1.1 什么是懒加载
懒加载(Lazy Loading)是一种优化技术,它可以:
- 延迟加载非必需的组件,只有你需要加载的时候加载,或者在指定时间预加载,比如首屏渲染后再预加载主要组件,当路由到主要组件时,主要组件已经在首屏渲染后预加载好了。
- 减小初始包的大小
- 提高应用的首次加载性能
1.2 React.lazy 和 Suspense
// React.lazy 用于动态导入组件
// Suspense 用于包装懒加载组件,提供加载状态
import React, { lazy, Suspense } from 'react';
// 懒加载组件
const LazyComponent = lazy(() => import('./LazyComponent'));
// 使用 Suspense 包装
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
2. 路由懒加载实现
2.1 基本路由懒加载
// src/router/index.js
import React, { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
// 懒加载路由组件
const Home = lazy(() => import('../pages/Home'));
const About = lazy(() => import('../pages/About'));
const User = lazy(() => import('../pages/User'));
// 路由配置
function AppRouter() {
return (
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/user" element={<User />} />
</Routes>
</Suspense>
);
}
export default AppRouter;
2.2 带加载组件的实现
// src/components/LoadingSpinner.js
function LoadingSpinner() {
return (
<div className="loading-spinner">
<div className="spinner"></div>
<p>Loading...</p>
</div>
);
}
// src/router/index.js
import LoadingSpinner from '../components/LoadingSpinner';
function AppRouter() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
{/* 路由配置 */}
</Routes>
</Suspense>
);
}
2.3 错误边界处理
// src/components/ErrorBoundary.js
class ErrorBoundary extends React.Component {
state = { hasError: false };
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.error('路由加载错误:', error, errorInfo);
}
render() {
if (this.state.hasError) {
return <div>Something went wrong!</div>;
}
return this.props.children;
}
}
// 在路由中使用
function AppRouter() {
return (
<ErrorBoundary>
<Suspense fallback={<LoadingSpinner />}>
<Routes>
{/* 路由配置 */}
</Routes>
</Suspense>
</ErrorBoundary>
);
}
3. 高级配置
3.1 路由配置对象化
// src/router/routes.js
import { lazy } from 'react';
const routes = [
{
path: '/',
component: lazy(() => import('../pages/Home')),
},
{
path: '/about',
component: lazy(() => import('../pages/About')),
},
{
path: '/user',
component: lazy(() => import('../pages/User')),
children: [
{
path: 'profile',
component: lazy(() => import('../pages/UserProfile')),
},
{
path: 'settings',
component: lazy(() => import('../pages/UserSettings')),
},
],
},
];
export default routes;
// src/router/index.js
import routes from './routes';
function renderRoutes(routes) {
return routes.map(route => (
<Route
key={route.path}
path={route.path}
element={
<Suspense fallback={<LoadingSpinner />}>
<route.component />
</Suspense>
}
>
{route.children && renderRoutes(route.children)}
</Route>
));
}
function AppRouter() {
return (
<ErrorBoundary>
<Routes>
{renderRoutes(routes)}
</Routes>
</ErrorBoundary>
);
}
3.2 预加载实现
效果: 提前加载组件代码,减少实际访问时的加载时间 当用户实际访问组件时,可以立即渲染,提升用户体验 使用场景: 首页必加载的核心组件 用户很可能访问的高频路由 较大的组件模块,需要提前加载以提升体验
// src/utils/preloadRoute.js
export const preloadRoute = (componentImport) => {
return () => {
const Component = lazy(componentImport);
componentImport(); // 触发预加载
return Component;
};
};
// 使用预加载
const routes = [
{
path: '/',
component: preloadRoute(() => import('../pages/Home')),
},
// ...其他路由
];
// 在导航时预加载
function NavLink({ to, children }) {
const handleMouseEnter = () => {
const route = routes.find(r => r.path === to);
if (route) {
const Component = route.component();
}
};
return (
<Link
to={to}
onMouseEnter={handleMouseEnter}
>
{children}
</Link>
);
}
效果: 当用户鼠标悬停在导航链接上时,预加载对应的组件 用户点击链接时,组件已经被加载,可以快速切换 使用场景: 导航菜单项 重要的交互按钮 用户可能点击的关键链接
3.3 加载状态优化
// src/components/LoadingFallback.js
function LoadingFallback({ delay = 200 }) {
const [showLoading, setShowLoading] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setShowLoading(true);
}, delay);
return () => clearTimeout(timer);
}, [delay]);
if (!showLoading) {
return null;
}
return <LoadingSpinner />;
}
// 在路由中使用
function AppRouter() {
return (
<ErrorBoundary>
<Suspense fallback={<LoadingFallback delay={200} />}>
<Routes>
{/* 路由配置 */}
</Routes>
</Suspense>
</ErrorBoundary>
);
}
4. 性能优化
4.1 分包策略
// 按功能模块分包
const AdminModule = lazy(() => import(
/* webpackChunkName: "admin" */
'../modules/Admin'
));
const UserModule = lazy(() => import(
/* webpackChunkName: "user" */
'../modules/User'
));
// 按路由层级分包
const routes = [
{
path: '/admin/*',
component: AdminModule,
},
{
path: '/user/*',
component: UserModule,
},
];
4.2 预加载策略
// 路由级别预加载
const preloadRoutes = () => {
const preloadComponent = (route) => {
if (route.component) {
const Component = route.component();
}
if (route.children) {
route.children.forEach(preloadComponent);
}
};
routes.forEach(preloadComponent);
};
// 在合适的时机触发预加载
// 例如:首屏加载完成后
window.addEventListener('load', () => {
// 使用 requestIdleCallback 在空闲时间预加载
if ('requestIdleCallback' in window) {
requestIdleCallback(() => preloadRoutes());
} else {
setTimeout(preloadRoutes, 1000);
}
});
效果: 在页面加载完成后的空闲时间预加载其他路由组件 不影响主要内容的加载和交互 充分利用浏览器空闲时间优化性能 使用场景: 非关键路由的预加载 大型应用的功能模块预加载 后台管理系统的模块预加载
5. 最佳实践
// 组合使用多种预加载策略
const AppRouter = () => {
useEffect(() => {
// 1. 立即预加载核心路由
preloadRoute(() => import('../pages/Home'))();
// 2. 空闲时间预加载次要路由
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
preloadRoute(() => import('../pages/Settings'))();
preloadRoute(() => import('../pages/Profile'))();
});
}
}, []);
return (
<Routes>
{/* 3. 交互式预加载其他路由 */}
<NavLink to="/dashboard" preload>
Dashboard
</NavLink>
</Routes>
);
};
使用建议:
- 按优先级预加载:
- 核心路由:立即预加载
- 重要路由:交互式预加载
- 次要路由:空闲时间预加载
- 注意资源消耗:
- 避免过度预加载
- 考虑用户的网络环境
- 监控预加载的性能影响
- 智能预加载:
- 基于用户行为预测
- 考虑设备和网络状况
- 动态调整预加载策略
- 性能监控:
- 监控预加载的成功率
- 跟踪加载时间
- 分析用户体验数据
5.1 组织结构
src/
├── router/
│ ├── index.js # 路由主配置
│ ├── routes.js # 路由表配置
│ └── guards.js # 路由守卫
├── components/
│ ├── LoadingSpinner.js # 加载组件
│ └── ErrorBoundary.js # 错误边界
└── pages/ # 页面组件
├── Home/
├── About/
└── User/
5.2 代码分割建议
- 按路由分割
- 按功能模块分割
- 按用户权限分割
- 避免过细的分割
5.3 性能优化建议
- 合理使用预加载
- 优化加载状态展示
- 实现错误重试机制
- 监控加载性能
6. 总结
6.1 核心要点
- 使用 React.lazy 实现组件懒加载
- 使用 Suspense 处理加载状态
- 配合 React Router 实现路由懒加载
- 实现预加载优化用户体验
6.2 使用建议
- 合理规划代码分割
- 优化加载体验
- 实现错误处理
- 注意性能监控