第 164 章:性能优化与最佳实践
学习目标
- 掌握 React 性能优化手段
- 学会 Profiler 工具使用
- 理解 Server Components 性能优势
- 学会代码分割与懒加载
一、React 性能模型
React 通过 Virtual DOM diff 决定哪些 DOM 需要更新。优化就是减少 diff 工作量和避免不必要的渲染。
二、React.memo 浅比较
tsx
import { memo } from 'react';
interface Props {
user: User;
onClick: (id: number) => void;
}
const UserCard = memo(function UserCard({ user, onClick }: Props) {
return (
<div onClick={() => onClick(user.id)}>
{user.name}
</div>
);
});触发重渲染的情况:
- Props 引用变化(对象/函数)
- Context value 变化
- 父组件重渲染
三、useMemo / useCallback
tsx
function Parent() {
const [count, setCount] = useState(0);
const [user, setUser] = useState<User>({ id: 1, name: 'Tom' });
// ❌ 每次新建对象
const config = { theme: 'dark', user };
// ✅ 引用稳定
const config = useMemo(() => ({ theme: 'dark', user }), [user]);
// ❌ 每次新建函数
const handleClick = (id: number) => console.log(id);
// ✅ 引用稳定
const handleClick = useCallback((id: number) => console.log(id), []);
return <Child config={config} onClick={handleClick} />;
}四、代码分割
4.1 路由级分割(Next.js 自动)
每个 app/路由/page.tsx 默认就是独立 chunk。
4.2 组件级分割
tsx
import { lazy, Suspense } from 'react';
const HeavyChart = lazy(() => import('./HeavyChart'));
function Dashboard() {
return (
<Suspense fallback={<div>加载图表...</div>}>
<HeavyChart />
</Suspense>
);
}4.3 动态 import
tsx
'use client';
import dynamic from 'next/dynamic';
const Chart = dynamic(() => import('./Chart'), {
loading: () => <p>加载中</p>,
ssr: false, // 禁用 SSR
});五、图片优化
tsx
import Image from 'next/image';
// 远程图片(需要在 next.config 配置域名)
<Image
src="https://example.com/x.jpg"
alt="描述"
width={800}
height={600}
priority // 首屏图片
/>
// 本地图片
import hero from './hero.jpg';
<Image src={hero} alt="hero" placeholder="blur" />自动优化:
- WebP / AVIF 格式
- 响应式尺寸
- 懒加载(默认)
六、字体优化
tsx
// app/layout.tsx
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN" className={inter.className}>
<body>{children}</body>
</html>
);
}七、避免常见反模式
7.1 列表虚拟化
tsx
// 长列表(>1000 项)用虚拟化
import { useVirtualizer } from '@tanstack/react-virtual';
function BigList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
});
return (
<div ref={parentRef} style={{ height: '400px', overflow: 'auto' }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map(v => (
<div key={v.key} style={{ transform: `translateY(${v.start}px)` }}>
{items[v.index].name}
</div>
))}
</div>
</div>
);
}7.2 避免内联函数(大列表)
tsx
// ❌ 每次渲染新建函数
{items.map(item => (
<Item key={item.id} onClick={() => handle(item)} />
))}
// ✅ 用 useCallback 或 data-属性
const handle = useCallback((e) => {
const id = e.currentTarget.dataset.id;
// ...
}, []);
{items.map(item => (
<Item key={item.id} data-id={item.id} onClick={handle} />
))}7.3 避免 Context 拆分不当
tsx
// ❌ 一个大 Context,任何字段变化都触发
<AppContext.Provider value={{ user, theme, count, ... }}>
// ✅ 拆分
<UserContext.Provider value={user}>
<ThemeContext.Provider value={theme}>
<CountContext.Provider value={count}>
{children}
</CountContext.Provider>
</ThemeContext.Provider>
</UserContext.Provider>八、Server Components 性能
- Server Components 在服务端运行,不发送到浏览器
- 大幅减少 JS bundle 体积
- 适合数据展示、不需要交互的部分
九、Web Vitals
| 指标 | 含义 | 目标 |
|---|---|---|
| LCP | 最大内容绘制 | < 2.5s |
| FID | 首次输入延迟 | < 100ms |
| CLS | 累积布局偏移 | < 0.1 |
| INP | 交互到下一次绘制 | < 200ms |
9.1 测量
tsx
'use client';
import { useReportWebVitals } from 'next/web-vitals';
export function WebVitals() {
useReportWebVitals((metric) => {
console.log(metric);
// 上报到分析服务
});
}十、Profiler 调试
tsx
import { Profiler } from 'react';
<Profiler id="Sidebar" onRender={(id, phase, actualDuration) => {
if (actualDuration > 16) {
console.warn(`${id} 渲染慢:${actualDuration}ms`);
}
}}>
<Sidebar />
</Profiler>十一、构建优化
js
// next.config.mjs
export default {
experimental: {
optimizePackageImports: ['antd', 'lodash'], // 按需引入
},
compiler: {
removeConsole: process.env.NODE_ENV === 'production', // 移除 console
},
};十二、本章小结
| 优化手段 | 场景 |
|---|---|
| React.memo | 纯展示组件 |
| useMemo | 重计算 |
| useCallback | 稳定函数引用 |
| 懒加载 | 大组件/路由 |
| 虚拟列表 | 长列表 |
| Server Components | 数据展示 |
| Image 组件 | 图片优化 |
动手练习
- 用 React.memo 优化一个列表
- 用 lazy 分割大组件
- 用 Profiler 找出慢组件
- 用 next/image 优化图片
- 测一下 Web Vitals
推荐阅读
下一章:第 165 章:实战总结与资源 →