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 => (<Routekey={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 (<Linkto={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 使用建议
- 合理规划代码分割
- 优化加载体验
- 实现错误处理
- 注意性能监控