name: performance-fundamentals description: 审查性能,包括N+1查询、重新渲染、可扩展性。当初级开发者问“这性能好吗”、“这能扩展吗”、“太慢了”时使用,或处理循环、大型列表、分页、缓存时使用。
性能基础审查
“过早优化是万恶之源,但成熟的忽视更糟。”
何时应用
在审查以下内容时激活此技能:
- 数据库查询(尤其是在循环中)
- React/Vue渲染逻辑
- API响应负载
- 数据转换
- 文件操作
- 缓存决策
审查清单
数据库性能
- [ ] 无N+1查询:相关记录是否批量获取,而非循环?
- [ ] 索引:频繁查询的字段是否已索引?
- [ ] 分页:列表端点是否对结果进行分页?
- [ ] 仅选择所需字段:是否不必要地获取整个记录?
前端性能
- [ ] 记忆化:昂贵的计算是否已缓存?
- [ ] 防止重新渲染:状态变化是否会导致不必要的重新渲染?
- [ ] 捆绑包大小:重型库是否懒加载?
- [ ] 图像优化:图像是否适当调整大小和格式化?
API性能
- [ ] 响应大小:负载是否最小化?
- [ ] 压缩:是否启用gzip/brotli?
- [ ] 缓存头:可缓存响应是否标记?
- [ ] 异步处理:慢速操作是否已排队?
内存与资源
- [ ] 清理:订阅/计时器是否已清理?
- [ ] 内存泄漏:事件监听器是否已移除?
- [ ] 连接池:数据库连接是否重用?
常见错误(反模式)
1. N+1查询问题
❌ const users = await User.findAll();
for (const user of users) {
user.posts = await Post.findByUserId(user.id); // N次查询!
}
✅ const users = await User.findAll({
include: [{ model: Post }] // 1次查询,使用JOIN
});
2. 不必要的重新渲染
❌ function Parent() {
const handleClick = () => {}; // 每次渲染创建新函数
return <Child onClick={handleClick} />;
}
✅ function Parent() {
const handleClick = useCallback(() => {}, []);
return <Child onClick={handleClick} />;
}
3. 在渲染中计算
❌ function UserList({ users }) {
// 每次渲染都运行
const sorted = users.sort((a, b) => a.name.localeCompare(b.name));
return <ul>{sorted.map(...)}</ul>;
}
✅ function UserList({ users }) {
const sorted = useMemo(
() => [...users].sort((a, b) => a.name.localeCompare(b.name)),
[users]
);
return <ul>{sorted.map(...)}</ul>;
}
4. 获取所有数据
❌ GET /api/users → 返回10,000个用户,包含所有字段
✅ GET /api/users?page=1&limit=20&fields=id,name,email
5. 缺少清理
❌ useEffect(() => {
const interval = setInterval(fetchData, 5000);
// 无清理!永远运行。
}, []);
✅ useEffect(() => {
const interval = setInterval(fetchData, 5000);
return () => clearInterval(interval);
}, []);
苏格拉底式问题
向初级开发者问这些问题,而不是给出答案:
- 扩展性:“当有10,000个项目时会发生什么?1,000,000个?”
- 查询:“此操作进行了多少次数据库查询?”
- 重新渲染:“当此状态改变时,哪些组件会重新渲染?”
- 内存:“是否有任何东西在不再需要后仍持有引用?”
- 负载:“客户端是否需要所有这些数据?”
Big O 快速参考
| 模式 | 复杂度 | 示例 | 10,000个项目时 |
|---|---|---|---|
| 直接查找 | O(1) | map.get(key) |
1次操作 |
| 单循环 | O(n) | array.find() |
10,000次操作 |
| 嵌套循环 | O(n²) | for i { for j } |
100,000,000次操作 |
| 排序 | O(n log n) | array.sort() |
~130,000次操作 |
性能目标
| 指标 | 目标 | 测量工具 |
|---|---|---|
| 首字节时间 (TTFB) | < 600ms | DevTools网络 |
| 最大内容绘制 (LCP) | < 2.5s | Lighthouse |
| 首次输入延迟 (FID) | < 100ms | Lighthouse |
| 累积布局偏移 (CLS) | < 0.1 | Lighthouse |
| API响应时间 | < 200ms (p95) | 服务器指标 |
需要警惕的红旗
| 红旗 | 要问的问题 |
|---|---|
| 循环内的查询 | “我们能否将其批处理为一次查询?” |
| 无分页 | “如果有100,000条记录怎么办?” |
SELECT * |
“我们需要所有这些字段吗?” |
| localStorage中的大型JSON | “这会减慢页面加载吗?” |
| JSX中的内联函数 | “这会在每次渲染时创建新函数吗?” |
| 无清理的setInterval | “组件卸载时什么来清除它?” |
| 同步文件操作 | “这应该异步吗?” |
| 无加载状态 | “用户在等待时看到什么?” |
快速改进
- 添加索引到频繁查询的数据库列
- 分页所有列表端点
- 懒加载首屏以下内容
- 压缩API响应
- 缓存昂贵计算,使用useMemo
- 防抖搜索输入
- 虚拟化长列表(使用react-window)