feat: Initial agent-skills repo — 4 adapted skills for Mosaic Stack
Skills included: - pr-reviewer: Adapted for Gitea/GitHub via platform-aware scripts (dropped fetch_pr_data.py and add_inline_comment.py, kept generate_review_files.py) - code-review-excellence: Methodology and checklists (React, TS, Python, etc.) - vercel-react-best-practices: 57 rules for React/Next.js performance - tailwind-design-system: Tailwind CSS v4 patterns, CVA, design tokens New shell scripts added to ~/.claude/scripts/git/: - pr-diff.sh: Get PR diff (GitHub gh / Gitea API) - pr-metadata.sh: Get PR metadata as normalized JSON Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,752 @@
|
||||
# Performance Review Guide
|
||||
|
||||
性能审查指南,覆盖前端、后端、数据库、算法复杂度和 API 性能。
|
||||
|
||||
## 目录
|
||||
|
||||
- [前端性能 (Core Web Vitals)](#前端性能-core-web-vitals)
|
||||
- [JavaScript 性能](#javascript-性能)
|
||||
- [内存管理](#内存管理)
|
||||
- [数据库性能](#数据库性能)
|
||||
- [API 性能](#api-性能)
|
||||
- [算法复杂度](#算法复杂度)
|
||||
- [性能审查清单](#性能审查清单)
|
||||
|
||||
---
|
||||
|
||||
## 前端性能 (Core Web Vitals)
|
||||
|
||||
### 2024 核心指标
|
||||
|
||||
| 指标 | 全称 | 目标值 | 含义 |
|
||||
|------|------|--------|------|
|
||||
| **LCP** | Largest Contentful Paint | ≤ 2.5s | 最大内容绘制时间 |
|
||||
| **INP** | Interaction to Next Paint | ≤ 200ms | 交互响应时间(2024 年替代 FID)|
|
||||
| **CLS** | Cumulative Layout Shift | ≤ 0.1 | 累积布局偏移 |
|
||||
| **FCP** | First Contentful Paint | ≤ 1.8s | 首次内容绘制 |
|
||||
| **TBT** | Total Blocking Time | ≤ 200ms | 主线程阻塞时间 |
|
||||
|
||||
### LCP 优化检查
|
||||
|
||||
```javascript
|
||||
// ❌ LCP 图片懒加载 - 延迟关键内容
|
||||
<img src="hero.jpg" loading="lazy" />
|
||||
|
||||
// ✅ LCP 图片立即加载
|
||||
<img src="hero.jpg" fetchpriority="high" />
|
||||
|
||||
// ❌ 未优化的图片格式
|
||||
<img src="hero.png" /> // PNG 文件过大
|
||||
|
||||
// ✅ 现代图片格式 + 响应式
|
||||
<picture>
|
||||
<source srcset="hero.avif" type="image/avif" />
|
||||
<source srcset="hero.webp" type="image/webp" />
|
||||
<img src="hero.jpg" alt="Hero" />
|
||||
</picture>
|
||||
```
|
||||
|
||||
**审查要点:**
|
||||
- [ ] LCP 元素是否设置 `fetchpriority="high"`?
|
||||
- [ ] 是否使用 WebP/AVIF 格式?
|
||||
- [ ] 是否有服务端渲染或静态生成?
|
||||
- [ ] CDN 是否配置正确?
|
||||
|
||||
### FCP 优化检查
|
||||
|
||||
```html
|
||||
<!-- ❌ 阻塞渲染的 CSS -->
|
||||
<link rel="stylesheet" href="all-styles.css" />
|
||||
|
||||
<!-- ✅ 关键 CSS 内联 + 异步加载其余 -->
|
||||
<style>/* 首屏关键样式 */</style>
|
||||
<link rel="preload" href="styles.css" as="style" onload="this.onload=null;this.rel='stylesheet'" />
|
||||
|
||||
<!-- ❌ 阻塞渲染的字体 -->
|
||||
@font-face {
|
||||
font-family: 'CustomFont';
|
||||
src: url('font.woff2');
|
||||
}
|
||||
|
||||
<!-- ✅ 字体显示优化 -->
|
||||
@font-face {
|
||||
font-family: 'CustomFont';
|
||||
src: url('font.woff2');
|
||||
font-display: swap; /* 先用系统字体,加载后切换 */
|
||||
}
|
||||
```
|
||||
|
||||
### INP 优化检查
|
||||
|
||||
```javascript
|
||||
// ❌ 长任务阻塞主线程
|
||||
button.addEventListener('click', () => {
|
||||
// 耗时 500ms 的同步操作
|
||||
processLargeData(data);
|
||||
updateUI();
|
||||
});
|
||||
|
||||
// ✅ 拆分长任务
|
||||
button.addEventListener('click', async () => {
|
||||
// 让出主线程
|
||||
await scheduler.yield?.() ?? new Promise(r => setTimeout(r, 0));
|
||||
|
||||
// 分批处理
|
||||
for (const chunk of chunks) {
|
||||
processChunk(chunk);
|
||||
await scheduler.yield?.();
|
||||
}
|
||||
updateUI();
|
||||
});
|
||||
|
||||
// ✅ 使用 Web Worker 处理复杂计算
|
||||
const worker = new Worker('heavy-computation.js');
|
||||
worker.postMessage(data);
|
||||
worker.onmessage = (e) => updateUI(e.data);
|
||||
```
|
||||
|
||||
### CLS 优化检查
|
||||
|
||||
```css
|
||||
/* ❌ 未指定尺寸的媒体 */
|
||||
img { width: 100%; }
|
||||
|
||||
/* ✅ 预留空间 */
|
||||
img {
|
||||
width: 100%;
|
||||
aspect-ratio: 16 / 9;
|
||||
}
|
||||
|
||||
/* ❌ 动态插入内容导致布局偏移 */
|
||||
.ad-container { }
|
||||
|
||||
/* ✅ 预留固定高度 */
|
||||
.ad-container {
|
||||
min-height: 250px;
|
||||
}
|
||||
```
|
||||
|
||||
**CLS 审查清单:**
|
||||
- [ ] 图片/视频是否有 width/height 或 aspect-ratio?
|
||||
- [ ] 字体加载是否使用 `font-display: swap`?
|
||||
- [ ] 动态内容是否预留空间?
|
||||
- [ ] 是否避免在现有内容上方插入内容?
|
||||
|
||||
---
|
||||
|
||||
## JavaScript 性能
|
||||
|
||||
### 代码分割与懒加载
|
||||
|
||||
```javascript
|
||||
// ❌ 一次性加载所有代码
|
||||
import { HeavyChart } from './charts';
|
||||
import { PDFExporter } from './pdf';
|
||||
import { AdminPanel } from './admin';
|
||||
|
||||
// ✅ 按需加载
|
||||
const HeavyChart = lazy(() => import('./charts'));
|
||||
const PDFExporter = lazy(() => import('./pdf'));
|
||||
|
||||
// ✅ 路由级代码分割
|
||||
const routes = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: lazy(() => import('./pages/Dashboard')),
|
||||
},
|
||||
{
|
||||
path: '/admin',
|
||||
component: lazy(() => import('./pages/Admin')),
|
||||
},
|
||||
];
|
||||
```
|
||||
|
||||
### Bundle 体积优化
|
||||
|
||||
```javascript
|
||||
// ❌ 导入整个库
|
||||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
// ✅ 按需导入
|
||||
import debounce from 'lodash/debounce';
|
||||
import { format } from 'date-fns';
|
||||
|
||||
// ❌ 未使用 Tree Shaking
|
||||
export default {
|
||||
fn1() {},
|
||||
fn2() {}, // 未使用但被打包
|
||||
};
|
||||
|
||||
// ✅ 命名导出支持 Tree Shaking
|
||||
export function fn1() {}
|
||||
export function fn2() {}
|
||||
```
|
||||
|
||||
**Bundle 审查清单:**
|
||||
- [ ] 是否使用动态 import() 进行代码分割?
|
||||
- [ ] 大型库是否按需导入?
|
||||
- [ ] 是否分析过 bundle 大小?(webpack-bundle-analyzer)
|
||||
- [ ] 是否有未使用的依赖?
|
||||
|
||||
### 列表渲染优化
|
||||
|
||||
```javascript
|
||||
// ❌ 渲染大列表
|
||||
function List({ items }) {
|
||||
return (
|
||||
<ul>
|
||||
{items.map(item => <li key={item.id}>{item.name}</li>)}
|
||||
</ul>
|
||||
); // 10000 条数据 = 10000 个 DOM 节点
|
||||
}
|
||||
|
||||
// ✅ 虚拟列表 - 只渲染可见项
|
||||
import { FixedSizeList } from 'react-window';
|
||||
|
||||
function VirtualList({ items }) {
|
||||
return (
|
||||
<FixedSizeList
|
||||
height={400}
|
||||
itemCount={items.length}
|
||||
itemSize={35}
|
||||
>
|
||||
{({ index, style }) => (
|
||||
<div style={style}>{items[index].name}</div>
|
||||
)}
|
||||
</FixedSizeList>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
**大数据审查要点:**
|
||||
- [ ] 列表超过 100 项是否使用虚拟滚动?
|
||||
- [ ] 表格是否支持分页或虚拟化?
|
||||
- [ ] 是否有不必要的全量渲染?
|
||||
|
||||
---
|
||||
|
||||
## 内存管理
|
||||
|
||||
### 常见内存泄漏
|
||||
|
||||
#### 1. 未清理的事件监听
|
||||
|
||||
```javascript
|
||||
// ❌ 组件卸载后事件仍在监听
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
}, []);
|
||||
|
||||
// ✅ 清理事件监听
|
||||
useEffect(() => {
|
||||
window.addEventListener('resize', handleResize);
|
||||
return () => window.removeEventListener('resize', handleResize);
|
||||
}, []);
|
||||
```
|
||||
|
||||
#### 2. 未清理的定时器
|
||||
|
||||
```javascript
|
||||
// ❌ 定时器未清理
|
||||
useEffect(() => {
|
||||
setInterval(fetchData, 5000);
|
||||
}, []);
|
||||
|
||||
// ✅ 清理定时器
|
||||
useEffect(() => {
|
||||
const timer = setInterval(fetchData, 5000);
|
||||
return () => clearInterval(timer);
|
||||
}, []);
|
||||
```
|
||||
|
||||
#### 3. 闭包引用
|
||||
|
||||
```javascript
|
||||
// ❌ 闭包持有大对象引用
|
||||
function createHandler() {
|
||||
const largeData = new Array(1000000).fill('x');
|
||||
|
||||
return function handler() {
|
||||
// largeData 被闭包引用,无法被回收
|
||||
console.log(largeData.length);
|
||||
};
|
||||
}
|
||||
|
||||
// ✅ 只保留必要数据
|
||||
function createHandler() {
|
||||
const largeData = new Array(1000000).fill('x');
|
||||
const length = largeData.length; // 只保留需要的值
|
||||
|
||||
return function handler() {
|
||||
console.log(length);
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 未清理的订阅
|
||||
|
||||
```javascript
|
||||
// ❌ WebSocket/EventSource 未关闭
|
||||
useEffect(() => {
|
||||
const ws = new WebSocket('wss://...');
|
||||
ws.onmessage = handleMessage;
|
||||
}, []);
|
||||
|
||||
// ✅ 清理连接
|
||||
useEffect(() => {
|
||||
const ws = new WebSocket('wss://...');
|
||||
ws.onmessage = handleMessage;
|
||||
return () => ws.close();
|
||||
}, []);
|
||||
```
|
||||
|
||||
### 内存审查清单
|
||||
|
||||
```markdown
|
||||
- [ ] useEffect 是否都有清理函数?
|
||||
- [ ] 事件监听是否在组件卸载时移除?
|
||||
- [ ] 定时器是否被清理?
|
||||
- [ ] WebSocket/SSE 连接是否关闭?
|
||||
- [ ] 大对象是否及时释放?
|
||||
- [ ] 是否有全局变量累积数据?
|
||||
```
|
||||
|
||||
### 检测工具
|
||||
|
||||
| 工具 | 用途 |
|
||||
|------|------|
|
||||
| Chrome DevTools Memory | 堆快照分析 |
|
||||
| MemLab (Meta) | 自动化内存泄漏检测 |
|
||||
| Performance Monitor | 实时内存监控 |
|
||||
|
||||
---
|
||||
|
||||
## 数据库性能
|
||||
|
||||
### N+1 查询问题
|
||||
|
||||
```python
|
||||
# ❌ N+1 问题 - 1 + N 次查询
|
||||
users = User.objects.all() # 1 次查询
|
||||
for user in users:
|
||||
print(user.profile.bio) # N 次查询(每个用户一次)
|
||||
|
||||
# ✅ Eager Loading - 2 次查询
|
||||
users = User.objects.select_related('profile').all()
|
||||
for user in users:
|
||||
print(user.profile.bio) # 无额外查询
|
||||
|
||||
# ✅ 多对多关系用 prefetch_related
|
||||
posts = Post.objects.prefetch_related('tags').all()
|
||||
```
|
||||
|
||||
```javascript
|
||||
// TypeORM 示例
|
||||
// ❌ N+1 问题
|
||||
const users = await userRepository.find();
|
||||
for (const user of users) {
|
||||
const posts = await user.posts; // 每次循环都查询
|
||||
}
|
||||
|
||||
// ✅ Eager Loading
|
||||
const users = await userRepository.find({
|
||||
relations: ['posts'],
|
||||
});
|
||||
```
|
||||
|
||||
### 索引优化
|
||||
|
||||
```sql
|
||||
-- ❌ 全表扫描
|
||||
SELECT * FROM orders WHERE status = 'pending';
|
||||
|
||||
-- ✅ 添加索引
|
||||
CREATE INDEX idx_orders_status ON orders(status);
|
||||
|
||||
-- ❌ 索引失效:函数操作
|
||||
SELECT * FROM users WHERE YEAR(created_at) = 2024;
|
||||
|
||||
-- ✅ 范围查询可用索引
|
||||
SELECT * FROM users
|
||||
WHERE created_at >= '2024-01-01' AND created_at < '2025-01-01';
|
||||
|
||||
-- ❌ 索引失效:LIKE 前缀通配符
|
||||
SELECT * FROM products WHERE name LIKE '%phone%';
|
||||
|
||||
-- ✅ 前缀匹配可用索引
|
||||
SELECT * FROM products WHERE name LIKE 'phone%';
|
||||
```
|
||||
|
||||
### 查询优化
|
||||
|
||||
```sql
|
||||
-- ❌ SELECT * 获取不需要的列
|
||||
SELECT * FROM users WHERE id = 1;
|
||||
|
||||
-- ✅ 只查询需要的列
|
||||
SELECT id, name, email FROM users WHERE id = 1;
|
||||
|
||||
-- ❌ 大表无 LIMIT
|
||||
SELECT * FROM logs WHERE type = 'error';
|
||||
|
||||
-- ✅ 分页查询
|
||||
SELECT * FROM logs WHERE type = 'error' LIMIT 100 OFFSET 0;
|
||||
|
||||
-- ❌ 在循环中执行查询
|
||||
for id in user_ids:
|
||||
cursor.execute("SELECT * FROM users WHERE id = %s", (id,))
|
||||
|
||||
-- ✅ 批量查询
|
||||
cursor.execute("SELECT * FROM users WHERE id IN %s", (tuple(user_ids),))
|
||||
```
|
||||
|
||||
### 数据库审查清单
|
||||
|
||||
```markdown
|
||||
🔴 必须检查:
|
||||
- [ ] 是否存在 N+1 查询?
|
||||
- [ ] WHERE 子句列是否有索引?
|
||||
- [ ] 是否避免了 SELECT *?
|
||||
- [ ] 大表查询是否有 LIMIT?
|
||||
|
||||
🟡 建议检查:
|
||||
- [ ] 是否使用了 EXPLAIN 分析查询计划?
|
||||
- [ ] 复合索引列顺序是否正确?
|
||||
- [ ] 是否有未使用的索引?
|
||||
- [ ] 是否有慢查询日志监控?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API 性能
|
||||
|
||||
### 分页实现
|
||||
|
||||
```javascript
|
||||
// ❌ 返回全部数据
|
||||
app.get('/users', async (req, res) => {
|
||||
const users = await User.findAll(); // 可能返回 100000 条
|
||||
res.json(users);
|
||||
});
|
||||
|
||||
// ✅ 分页 + 限制最大数量
|
||||
app.get('/users', async (req, res) => {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = Math.min(parseInt(req.query.limit) || 20, 100); // 最大 100
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
const { rows, count } = await User.findAndCountAll({
|
||||
limit,
|
||||
offset,
|
||||
order: [['id', 'ASC']],
|
||||
});
|
||||
|
||||
res.json({
|
||||
data: rows,
|
||||
pagination: {
|
||||
page,
|
||||
limit,
|
||||
total: count,
|
||||
totalPages: Math.ceil(count / limit),
|
||||
},
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 缓存策略
|
||||
|
||||
```javascript
|
||||
// ✅ Redis 缓存示例
|
||||
async function getUser(id) {
|
||||
const cacheKey = `user:${id}`;
|
||||
|
||||
// 1. 检查缓存
|
||||
const cached = await redis.get(cacheKey);
|
||||
if (cached) {
|
||||
return JSON.parse(cached);
|
||||
}
|
||||
|
||||
// 2. 查询数据库
|
||||
const user = await db.users.findById(id);
|
||||
|
||||
// 3. 写入缓存(设置过期时间)
|
||||
await redis.setex(cacheKey, 3600, JSON.stringify(user));
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
// ✅ HTTP 缓存头
|
||||
app.get('/static-data', (req, res) => {
|
||||
res.set({
|
||||
'Cache-Control': 'public, max-age=86400', // 24 小时
|
||||
'ETag': 'abc123',
|
||||
});
|
||||
res.json(data);
|
||||
});
|
||||
```
|
||||
|
||||
### 响应压缩
|
||||
|
||||
```javascript
|
||||
// ✅ 启用 Gzip/Brotli 压缩
|
||||
const compression = require('compression');
|
||||
app.use(compression());
|
||||
|
||||
// ✅ 只返回必要字段
|
||||
// 请求: GET /users?fields=id,name,email
|
||||
app.get('/users', async (req, res) => {
|
||||
const fields = req.query.fields?.split(',') || ['id', 'name'];
|
||||
const users = await User.findAll({
|
||||
attributes: fields,
|
||||
});
|
||||
res.json(users);
|
||||
});
|
||||
```
|
||||
|
||||
### 限流保护
|
||||
|
||||
```javascript
|
||||
// ✅ 速率限制
|
||||
const rateLimit = require('express-rate-limit');
|
||||
|
||||
const limiter = rateLimit({
|
||||
windowMs: 60 * 1000, // 1 分钟
|
||||
max: 100, // 最多 100 次请求
|
||||
message: { error: 'Too many requests, please try again later.' },
|
||||
});
|
||||
|
||||
app.use('/api/', limiter);
|
||||
```
|
||||
|
||||
### API 审查清单
|
||||
|
||||
```markdown
|
||||
- [ ] 列表接口是否有分页?
|
||||
- [ ] 是否限制了每页最大数量?
|
||||
- [ ] 热点数据是否有缓存?
|
||||
- [ ] 是否启用了响应压缩?
|
||||
- [ ] 是否有速率限制?
|
||||
- [ ] 是否只返回必要字段?
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 算法复杂度
|
||||
|
||||
### 常见复杂度对比
|
||||
|
||||
| 复杂度 | 名称 | 10 条 | 1000 条 | 100 万条 | 示例 |
|
||||
|--------|------|-------|---------|----------|------|
|
||||
| O(1) | 常数 | 1 | 1 | 1 | 哈希查找 |
|
||||
| O(log n) | 对数 | 3 | 10 | 20 | 二分查找 |
|
||||
| O(n) | 线性 | 10 | 1000 | 100 万 | 遍历数组 |
|
||||
| O(n log n) | 线性对数 | 33 | 10000 | 2000 万 | 快速排序 |
|
||||
| O(n²) | 平方 | 100 | 100 万 | 1 万亿 | 嵌套循环 |
|
||||
| O(2ⁿ) | 指数 | 1024 | ∞ | ∞ | 递归斐波那契 |
|
||||
|
||||
### 代码审查中的识别
|
||||
|
||||
```javascript
|
||||
// ❌ O(n²) - 嵌套循环
|
||||
function findDuplicates(arr) {
|
||||
const duplicates = [];
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
for (let j = i + 1; j < arr.length; j++) {
|
||||
if (arr[i] === arr[j]) {
|
||||
duplicates.push(arr[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return duplicates;
|
||||
}
|
||||
|
||||
// ✅ O(n) - 使用 Set
|
||||
function findDuplicates(arr) {
|
||||
const seen = new Set();
|
||||
const duplicates = new Set();
|
||||
for (const item of arr) {
|
||||
if (seen.has(item)) {
|
||||
duplicates.add(item);
|
||||
}
|
||||
seen.add(item);
|
||||
}
|
||||
return [...duplicates];
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
// ❌ O(n²) - 每次循环都调用 includes
|
||||
function removeDuplicates(arr) {
|
||||
const result = [];
|
||||
for (const item of arr) {
|
||||
if (!result.includes(item)) { // includes 是 O(n)
|
||||
result.push(item);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ✅ O(n) - 使用 Set
|
||||
function removeDuplicates(arr) {
|
||||
return [...new Set(arr)];
|
||||
}
|
||||
```
|
||||
|
||||
```javascript
|
||||
// ❌ O(n) 查找 - 每次都遍历
|
||||
const users = [{ id: 1, name: 'A' }, { id: 2, name: 'B' }, ...];
|
||||
|
||||
function getUser(id) {
|
||||
return users.find(u => u.id === id); // O(n)
|
||||
}
|
||||
|
||||
// ✅ O(1) 查找 - 使用 Map
|
||||
const userMap = new Map(users.map(u => [u.id, u]));
|
||||
|
||||
function getUser(id) {
|
||||
return userMap.get(id); // O(1)
|
||||
}
|
||||
```
|
||||
|
||||
### 空间复杂度考虑
|
||||
|
||||
```javascript
|
||||
// ⚠️ O(n) 空间 - 创建新数组
|
||||
const doubled = arr.map(x => x * 2);
|
||||
|
||||
// ✅ O(1) 空间 - 原地修改(如果允许)
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
arr[i] *= 2;
|
||||
}
|
||||
|
||||
// ⚠️ 递归深度过大可能栈溢出
|
||||
function factorial(n) {
|
||||
if (n <= 1) return 1;
|
||||
return n * factorial(n - 1); // O(n) 栈空间
|
||||
}
|
||||
|
||||
// ✅ 迭代版本 O(1) 空间
|
||||
function factorial(n) {
|
||||
let result = 1;
|
||||
for (let i = 2; i <= n; i++) {
|
||||
result *= i;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
```
|
||||
|
||||
### 复杂度审查问题
|
||||
|
||||
```markdown
|
||||
💡 "这个嵌套循环的复杂度是 O(n²),数据量大时会有性能问题"
|
||||
🔴 "这里用 Array.includes() 在循环中,整体是 O(n²),建议用 Set"
|
||||
🟡 "这个递归深度可能导致栈溢出,建议改为迭代或尾递归"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能审查清单
|
||||
|
||||
### 🔴 必须检查(阻塞级)
|
||||
|
||||
**前端:**
|
||||
- [ ] LCP 图片是否懒加载?(不应该)
|
||||
- [ ] 是否有 `transition: all`?
|
||||
- [ ] 是否动画 width/height/top/left?
|
||||
- [ ] 列表 >100 项是否虚拟化?
|
||||
|
||||
**后端:**
|
||||
- [ ] 是否存在 N+1 查询?
|
||||
- [ ] 列表接口是否有分页?
|
||||
- [ ] 是否有 SELECT * 查大表?
|
||||
|
||||
**通用:**
|
||||
- [ ] 是否有 O(n²) 或更差的嵌套循环?
|
||||
- [ ] useEffect/事件监听是否有清理?
|
||||
|
||||
### 🟡 建议检查(重要级)
|
||||
|
||||
**前端:**
|
||||
- [ ] 是否使用代码分割?
|
||||
- [ ] 大型库是否按需导入?
|
||||
- [ ] 图片是否使用 WebP/AVIF?
|
||||
- [ ] 是否有未使用的依赖?
|
||||
|
||||
**后端:**
|
||||
- [ ] 热点数据是否有缓存?
|
||||
- [ ] WHERE 列是否有索引?
|
||||
- [ ] 是否有慢查询监控?
|
||||
|
||||
**API:**
|
||||
- [ ] 是否启用响应压缩?
|
||||
- [ ] 是否有速率限制?
|
||||
- [ ] 是否只返回必要字段?
|
||||
|
||||
### 🟢 优化建议(建议级)
|
||||
|
||||
- [ ] 是否分析过 bundle 大小?
|
||||
- [ ] 是否使用 CDN?
|
||||
- [ ] 是否有性能监控?
|
||||
- [ ] 是否做过性能基准测试?
|
||||
|
||||
---
|
||||
|
||||
## 性能度量阈值
|
||||
|
||||
### 前端指标
|
||||
|
||||
| 指标 | 好 | 需改进 | 差 |
|
||||
|------|-----|--------|-----|
|
||||
| LCP | ≤ 2.5s | 2.5-4s | > 4s |
|
||||
| INP | ≤ 200ms | 200-500ms | > 500ms |
|
||||
| CLS | ≤ 0.1 | 0.1-0.25 | > 0.25 |
|
||||
| FCP | ≤ 1.8s | 1.8-3s | > 3s |
|
||||
| Bundle Size (JS) | < 200KB | 200-500KB | > 500KB |
|
||||
|
||||
### 后端指标
|
||||
|
||||
| 指标 | 好 | 需改进 | 差 |
|
||||
|------|-----|--------|-----|
|
||||
| API 响应时间 | < 100ms | 100-500ms | > 500ms |
|
||||
| 数据库查询 | < 50ms | 50-200ms | > 200ms |
|
||||
| 页面加载 | < 3s | 3-5s | > 5s |
|
||||
|
||||
---
|
||||
|
||||
## 工具推荐
|
||||
|
||||
### 前端性能
|
||||
|
||||
| 工具 | 用途 |
|
||||
|------|------|
|
||||
| [Lighthouse](https://developer.chrome.com/docs/lighthouse/) | Core Web Vitals 测试 |
|
||||
| [WebPageTest](https://www.webpagetest.org/) | 详细性能分析 |
|
||||
| [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) | Bundle 分析 |
|
||||
| [Chrome DevTools Performance](https://developer.chrome.com/docs/devtools/performance/) | 运行时性能分析 |
|
||||
|
||||
### 内存检测
|
||||
|
||||
| 工具 | 用途 |
|
||||
|------|------|
|
||||
| [MemLab](https://github.com/facebookincubator/memlab) | 自动化内存泄漏检测 |
|
||||
| Chrome Memory Tab | 堆快照分析 |
|
||||
|
||||
### 后端性能
|
||||
|
||||
| 工具 | 用途 |
|
||||
|------|------|
|
||||
| EXPLAIN | 数据库查询计划分析 |
|
||||
| [pganalyze](https://pganalyze.com/) | PostgreSQL 性能监控 |
|
||||
| [New Relic](https://newrelic.com/) / [Datadog](https://www.datadoghq.com/) | APM 监控 |
|
||||
|
||||
---
|
||||
|
||||
## 参考资源
|
||||
|
||||
- [Core Web Vitals - web.dev](https://web.dev/articles/vitals)
|
||||
- [Optimizing Core Web Vitals - Vercel](https://vercel.com/guides/optimizing-core-web-vitals-in-2024)
|
||||
- [MemLab - Meta Engineering](https://engineering.fb.com/2022/09/12/open-source/memlab/)
|
||||
- [Big O Cheat Sheet](https://www.bigocheatsheet.com/)
|
||||
- [N+1 Query Problem - Stack Overflow](https://stackoverflow.com/questions/97197/what-is-the-n1-selects-problem-in-orm-object-relational-mapping)
|
||||
- [API Performance Optimization](https://algorithmsin60days.com/blog/optimizing-api-performance/)
|
||||
Reference in New Issue
Block a user