Development
性能优化
优化应用性能。在存在性能要求、怀疑出现性能回退,或需要改进 Core Web Vitals 时使用。
先度量,再优化。没有度量的性能工作只是凭空猜测。
使用场景
- 规范中存在性能要求
- 用户或监控反映出现了缓慢行为
- Core Web Vitals 分数低于阈值
- 你怀疑某次变更引入了性能回退
- 构建处理大数据集或高流量的功能
Core Web Vitals 目标
| 指标 | 良好(Good) | 需改进(Needs Improvement) | 差(Poor) |
|---|---|---|---|
| LCP(最大内容绘制) | ≤ 2.5s | ≤ 4.0s | > 4.0s |
| INP(下一次绘制交互) | ≤ 200ms | ≤ 500ms | > 500ms |
| CLS(累积布局偏移) | ≤ 0.1 | ≤ 0.25 | > 0.25 |
优化工作流
1. MEASURE → Establish baseline with real data
2. IDENTIFY → Find the actual bottleneck (not assumed)
3. FIX → Address the specific bottleneck
4. VERIFY → Measure again, confirm improvement
5. GUARD → Add monitoring or tests to prevent regression
第 1 步:度量
两种互补的方法 —— 两者都要用:
- 合成监测(Lighthouse、DevTools): 受控条件,可复现
- 真实用户监测 RUM(web-vitals 库、CrUX): 真实条件下的真实用户数据
前端:
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(console.log);
onINP(console.log);
onCLS(console.log);
后端:
console.time('db-query');
const result = await db.query(...);
console.timeEnd('db-query');
第 2 步:定位瓶颈
前端:
| 症状 | 可能原因 | 排查方式 |
|---|---|---|
| LCP 缓慢 | 大图片、阻塞渲染的资源 | 检查网络瀑布图、图片尺寸 |
| CLS 偏高 | 未指定尺寸的图片、延迟加载的内容 | 检查布局偏移归因 |
| INP 较差 | 主线程上有繁重的 JavaScript | 在性能跟踪中检查长任务 |
后端:
| 症状 | 可能原因 | 排查方式 |
|---|---|---|
| API 响应缓慢 | N+1 查询、缺少索引 | 检查数据库查询日志 |
| 内存增长 | 引用泄漏、无界缓存 | 堆快照分析 |
| CPU 飙升 | 同步的繁重计算 | CPU 性能剖析 |
第 3 步:修复常见反模式
N+1 查询(后端)
// BAD: N+1 — one query per task for the owner
const tasks = await db.tasks.findMany();
for (const task of tasks) {
task.owner = await db.users.findUnique({ where: { id: task.ownerId } });
}
// GOOD: Single query with join/include
const tasks = await db.tasks.findMany({
include: { owner: true },
});
无界数据获取
// BAD: Fetching all records
const allTasks = await db.tasks.findMany();
// GOOD: Paginated with limits
const tasks = await db.tasks.findMany({
take: 20,
skip: (page - 1) * 20,
orderBy: { createdAt: 'desc' },
});
不必要的重渲染(React)
// BAD: Creates new object on every render
function TaskList() {
return <TaskFilters options={{ sortBy: 'date', order: 'desc' }} />;
}
// GOOD: Stable reference
const DEFAULT_OPTIONS = { sortBy: 'date', order: 'desc' } as const;
function TaskList() {
return <TaskFilters options={DEFAULT_OPTIONS} />;
}
// Use React.memo for expensive components
const TaskItem = React.memo(function TaskItem({ task }: Props) {
return <div>{/* expensive render */}</div>;
});
// Use useMemo for expensive computations
function TaskStats({ tasks }: Props) {
const stats = useMemo(() => calculateStats(tasks), [tasks]);
return <div>{stats.completed} / {stats.total}</div>;
}
包体积过大
// Dynamic import for heavy, rarely-used features
const ChartLibrary = lazy(() => import('./ChartLibrary'));
// Route-level code splitting
const SettingsPage = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<Spinner />}>
<SettingsPage />
</Suspense>
);
}
缺少缓存(后端)
// Cache frequently-read, rarely-changed data
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
let cachedConfig: AppConfig | null = null;
let cacheExpiry = 0;
async function getAppConfig(): Promise<AppConfig> {
if (cachedConfig && Date.now() < cacheExpiry) {
return cachedConfig;
}
cachedConfig = await db.config.findFirst();
cacheExpiry = Date.now() + CACHE_TTL;
return cachedConfig;
}
性能预算
设定预算并强制执行:
JavaScript bundle: < 200KB gzipped (initial load)
CSS: < 50KB gzipped
Images: < 200KB per image (above the fold)
Fonts: < 100KB total
API response time: < 200ms (p95)
Time to Interactive: < 3.5s on 4G
Lighthouse Performance score: ≥ 90
验证
在任何与性能相关的变更之后:
- 存在变更前后的度量数据(具体数字)
- 已定位并处理了具体瓶颈
- Core Web Vitals 处于「Good」阈值内
- 包体积未显著增加
- 新的数据获取代码中无 N+1 查询
- 性能预算在 CI 中通过(如已配置)
- 现有测试仍然通过