背景介绍
去年我们接手了一个企业管理后台项目,技术栈是 React + TypeScript。用户反馈页面加载慢,首屏加载时间超过 8 秒。通过 Chrome DevTools 分析,发现主要问题在于:
- JS bundle 过大(超过 2MB)
- 长列表渲染导致卡顿
- 组件重复渲染严重
经过一系列优化,我们将首屏加载时间降到了 1.5 秒以内。下面分享具体的优化方案。
一、代码分割与懒加载
问题分析:初始 bundle 包含了所有页面的代码,即使用户只访问首页,也要加载所有路由的组件。
解决方案:使用 React.lazy 和 Suspense 实现路由级别的代码分割。
// 优化前
import Dashboard from './pages/Dashboard';
import Settings from './pages/Settings';
// 优化后
const Dashboard = React.lazy(() => import('./pages/Dashboard'));
const Settings = React.lazy(() => import('./pages/Settings'));
// 在路由中使用
<Suspense fallback={<Loading />}>
<Route path="/dashboard" component={Dashboard} />
</Suspense>
效果:首屏 JS 体积从 2.1MB 降到 800KB
二、虚拟列表优化
问题分析:数据表格包含 1000+ 条数据,一次性渲染导致严重卡顿。
解决方案:使用 react-window 实现虚拟滚动,只渲染可见区域的内容。
import { FixedSizeList as List } from 'react-window';
const Row = ({ index, style }) => (
<div style={style}>{data[index].name}</div>
);
const VirtualList = () => (
<List
height={400}
itemCount={data.length}
itemSize={50}
width="100%"
>
{Row}
</List>
);
效果:长列表渲染时间从 300ms 降到 10ms 以内
三、避免不必要的重渲染
问题分析:通过 React DevTools 的 Profiler 发现,很多组件在父组件更新时被不必要地重新渲染。
解决方案:
- 使用 memo 包裹组件:对于纯展示组件,使用 React.memo 进行浅比较
- 使用 useMemo 缓存计算结果:避免每次渲染都重复计算
- 使用 useCallback 缓存函数引用:避免子组件因函数引用变化而重渲染
// 使用 memo
const UserCard = React.memo(({ user }) => (
<div>{user.name}</div>
));
// 使用 useMemo
const filteredUsers = useMemo(() => {
return users.filter(u => u.age > 18);
}, [users]);
// 使用 useCallback
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);
四、构建优化
问题分析:打包后的文件包含很多未使用的代码和重复依赖。
解决方案:
- Tree Shaking:确保使用 ES modules,配置 sideEffects
- 代码压缩:使用 TerserPlugin 压缩 JS
- 图片优化:使用 Webpack 插件压缩图片,使用 WebP 格式
- CDN 加速:将静态资源部署到 CDN
- Gzip/Brotli 压缩:配置服务器启用压缩
五、其他优化技巧
- 使用 CSS-in-JS 的注意事项:避免在 render 中创建样式对象
- 事件监听优化:使用 useLayoutEffect 添加事件监听,记得清理
- 状态管理优化:合理划分 state,避免不必要的全局状态
- 使用 Fragment:避免多余的 DOM 节点
优化效果对比
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| 首屏加载时间 | 8.2s | 1.4s | -83% |
| JS Bundle 大小 | 2.1MB | 780KB | -63% |
| 长列表渲染时间 | 320ms | 8ms | -97% |
总结
React 性能优化是一个系统性工程,需要从多个层面入手:
- 测量先行:使用 Chrome DevTools 和 React Profiler 定位瓶颈
- 代码层面:合理使用 memo、useMemo、useCallback
- 架构层面:代码分割、虚拟列表
- 构建层面:Tree Shaking、压缩、CDN
优化是持续的过程,需要定期review性能指标,不断迭代改进。