This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/REACT/5. 工程实践篇/15-性能优化.md
T
2026-04-29 22:19:51 +08:00

158 lines
4.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [React, Performance, Optimization, Frontend]
create time: 2026-04-29 22:14
---
# 性能优化
## 概述
React 性能优化的核心原则是 **"减少不必要的渲染"**。本文档从诊断工具到具体手段,提供完整的调优方法论。
## 性能诊断工具箱
```mermaid
graph TB
A[发现性能问题] --> B["选择诊断工具"]
B --> C["React DevTools Profiler"]
B --> D["Chrome Performance Tab"]
B --> E["Lighthouse"]
B --> F["Web Vitals 监控"]
C --> G["定位重渲染的组件和原因"]
D --> H["分析主线程阻塞时段"]
E --> I["整体 LCP/FID/CLS 评分"]
F --> J["生产环境真实用户数据"]
style C fill:#61DAFB,color:#000
style H fill:#F5A87D,color:#000
```
### React DevTools Profiler 使用要点
1. 录制期间进行关键交互(点击、输入)
2. 观察 **Commit 颜色** —— 红色越深表示重渲染越多
3. 展开组件树,关注 "why did this render" 原因
4. 对比优化前后的 Commit 时间变化
## React.memo —— 阻止子组件重渲染
```tsx
const ExpensiveList = React.memo(({ items }: { items: Item[] }) => {
// props.items 引用不变时,跳过整个子树的 re-render
return (
<ul>
{items.map(item => <li key={item.id}>{item.name}</li>)}
</ul>
);
}, (prevProps, nextProps) => prevProps.items === nextProps.items); // 自定义比较函数
```
> [!warning] React.memo 的适用边界
> - 只对**纯组件**有用——相同 props 必须产生相同的输出
> - 父组件每次传新对象/函数作 prop → memo 无效
> - 简单列表(几十项以内)不需要 memo
## 虚拟列表(Virtual Scrolling)
当列表项超过数百条时,虚拟滚动通过只渲染可视区域内的 DOM 元素来大幅降低内存占用:
```tsx
// 方案1:tanstack/virtual(推荐)
import { useVirtualizer } from "@tanstack/react-virtual";
function VirtualList({ items }: { items: string[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50, // 预估每项高度
overscan: 5, // 视口上下各多渲染 5 项
});
return (
<div ref={parentRef} style={{ height: 400, overflow: "auto" }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: "relative" }}>
{virtualizer.getVirtualItems().map(virtualRow => (
<div
key={virtualRow.index}
style={{
position: "absolute",
top: 0,
left: 0,
width: "100%",
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
{items[virtualRow.index]}
</div>
))}
</div>
</div>
);
}
```
> [!tip] 何时需要虚拟列表?
> - 列表项 > ~50 且每帧渲染耗时可感知
> - 固定高度的项目比可变高度更容易实现
> - React Window / React Virtualized 是老牌的成熟方案
## Code Splitting
### 路由级拆分
```tsx
// Next.js App Router(内置)
const AdminPage = lazy(() => import("./pages/Admin"));
// vite + React Router
const Settings = lazy(() => import(/* vite: preload */ "./pages/Settings"));
```
### 组件级拆分
```tsx
import dynamic from "next/dynamic";
// 不加载图表库直到真正需要
const Chart = dynamic(() => import("recharts"), { ssr: false });
// 或带 loading fallback
const HeavyEditor = dynamic(() => import("@monaco-editor/react"), {
loading: () => <p>Loading editor...</p>,
ssr: false,
});
```
### Bundle 分析与优化
```bash
# 安装插件
npm install --save-dev rollup-plugin-visualizer
# 或在 Next.js 中使用 next-bundle-analyzer
# 生成可视化报告
npx run build && npx visualizer
```
> [!tip] Bundle 大小目标
> | 层级 | 目标大小(gzipped)|
> |------|---------------------|
> | 首屏 chunk | < 150KB |
> | 单个 chunk | < 300KB |
> | JS Total | < 500KB(SPA)/ < 200KB(PWA) |
## Lighthouse 关键指标调优
| 指标 | 含义 | 优化方向 |
|------|------|----------|
| **FCP**(First Contentful Paint) | 首次内容绘制 | 减小首屏 HTML/JS 体积 |
| **LCP**(Largest Contentful Paint) | 最大内容绘制 | 图片懒加载、预加载关键资源 |
| **INP**(Interaction to Next Paint) | 交互响应延迟 | useTransition、删除同步 heavy work |
| **CLS**(Cumulative Layout Shift) | 布局偏移 | 预留图片宽高、避免字体闪烁 |
## 关联笔记