三种性能瓶颈
| 类型 | 表现 | 解决方向 |
|---|---|---|
| 加载慢 | 首屏空白久 | bundle 拆分、SSR、图片优化 |
| 交互卡 | 点击/输入有迟滞 | 减少 re-render、防抖、懒加载 |
| 滚动顿 | 长列表滑不动 | 虚拟化、骨架占位 |
每种用法都不一样,先确认你是哪一种。
测量工具
- React DevTools Profiler:录一次操作 → 看每个组件 render 耗时
- Chrome Performance:录一次操作 → 看主线程 long task
- Lighthouse:跑一次 → 看 LCP / FID / CLS 三大指标
- Web Vitals 库:生产环境采集真实用户数据
减少 re-render
找到无效 re-render
DevTools Profiler 录一次 → 看哪些组件 render 时间 > 0 但 props 没变(高亮显示"Why did this render")。
解法 1:state 下沉
function App() {
const [text, setText] = useState('');
return (
<>
<input value={text} onChange={e => setText(e.target.value)} />
<HeavyChart /> {/* text 变 → 整个 App re-render → HeavyChart 也 */}
</>
);
}
修:把 input 拆出去
function App() {
return <><MyInput /><HeavyChart /></>;
}
function MyInput() {
const [text, setText] = useState('');
return <input value={text} onChange={e => setText(e.target.value)} />;
}
text 现在住在 MyInput——它变 HeavyChart 完全不受影响。这是最常被忽略的优化。
解法 2:memo + useCallback
只在子组件确实昂贵且 props 引用稳定才有效。详见 第 08 篇。
解法 3:React Compiler(推荐)
// next.config.js(Next.js 15+ 起从 experimental 提到顶层;以官方文档为准)
{ reactCompiler: true }
编译器自动加 memo。新项目优先开启,配合 eslint-plugin-react-compiler 修违反 React Rules 的代码。详见 第 08 篇。
Bundle 大小
# Next.js 自带
npm run build # 看输出,每条路由的 First Load JS
或 @next/bundle-analyzer 可视化。
减小手段
- 动态 import:
const Heavy = dynamic(() => import('./Heavy'))——只在用到时加载 - 避免 barrel file:
import { x } from '@/lib'拉一堆——直接import { x } from '@/lib/x' - 替换大库:
moment(200kb) →date-fns/dayjs(~10kb);lodash整包 →lodash-es按需 - 图片优化:Next.js
<Image>自动生成 WebP / AVIF + 多尺寸 - 字体优化:
next/font自动 self-host + 子集化
长列表虚拟化
10k 行表格、聊天历史——只渲染可视区域 + buffer:
npm i @tanstack/react-virtual
const rowVirtualizer = useVirtualizer({
count: 10000,
getScrollElement: () => parentRef.current,
estimateSize: () => 40,
});
return (
<div ref={parentRef} style={{ height: 500, overflow: 'auto' }}>
<div style={{ height: rowVirtualizer.getTotalSize(), position: 'relative' }}>
{rowVirtualizer.getVirtualItems().map(v => (
<div key={v.key} style={{
position: 'absolute', top: 0, transform: `translateY(${v.start}px)`,
}}>
{data[v.index]}
</div>
))}
</div>
</div>
);
输入防抖 / 节流
const debouncedQ = useDebounce(query, 300);
useEffect(() => { search(debouncedQ); }, [debouncedQ]);
用户连续输入只触发一次搜索。
useTransition 替代法(React 18+):
const [isPending, startTransition] = useTransition();
const [filter, setFilter] = useState('');
const onChange = (e) => {
setFilter(e.target.value); // 紧急 update
startTransition(() => {
setFilteredList(heavyFilter(e.target.value)); // 非紧急
});
};
紧急更新(输入框跟手)和非紧急更新(重新过滤大列表)分离——输入永远顺滑。
Web Vitals
监控真实用户体验:
// app/web-vitals.ts
'use client';
import { useReportWebVitals } from 'next/web-vitals';
export function WebVitals() {
useReportWebVitals((metric) => {
fetch('/api/vitals', { method: 'POST', body: JSON.stringify(metric) });
});
}
关注:
- LCP < 2.5s(最大内容绘制)
- INP < 200ms(交互到下次绘制)
- CLS < 0.1(视觉稳定性)
服务端优化
慢主要不在 React 而在数据库 / 后端:
- DB 加索引
- N+1 → join / dataloader
- 缓存(Redis / Next.js Data Cache)
- CDN
90% 的"网站卡"问题其实在后端。先看后端响应时间,再优化前端。
性能优化的优先级
1. 先测、确认瓶颈
2. 算法 / 数据结构 / 缓存
3. 减少 React re-render(state 下沉、memo)
4. Bundle 拆分、懒加载
5. 渲染策略(SSR / SSG / ISR)
6. 微观优化(避免内联对象、useCallback)
倒着来 = 浪费时间。
→ 下一篇 SSR / CSR / SSG / ISR