code-abyss 1.6.16 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/skills/SKILL.md +24 -16
- package/skills/domains/ai/SKILL.md +2 -2
- package/skills/domains/ai/prompt-and-eval.md +279 -0
- package/skills/domains/architecture/SKILL.md +2 -3
- package/skills/domains/architecture/security-arch.md +87 -0
- package/skills/domains/data-engineering/SKILL.md +188 -26
- package/skills/domains/development/SKILL.md +1 -4
- package/skills/domains/devops/SKILL.md +3 -5
- package/skills/domains/devops/performance.md +63 -0
- package/skills/domains/devops/testing.md +97 -0
- package/skills/domains/frontend-design/SKILL.md +12 -3
- package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
- package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
- package/skills/domains/frontend-design/engineering.md +287 -0
- package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
- package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
- package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
- package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
- package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
- package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
- package/skills/domains/infrastructure/SKILL.md +174 -34
- package/skills/domains/mobile/SKILL.md +211 -21
- package/skills/domains/orchestration/SKILL.md +1 -0
- package/skills/domains/security/SKILL.md +4 -6
- package/skills/domains/security/blue-team.md +57 -0
- package/skills/domains/security/red-team.md +54 -0
- package/skills/domains/security/threat-intel.md +50 -0
- package/skills/orchestration/multi-agent/SKILL.md +195 -46
- package/skills/run_skill.js +134 -0
- package/skills/tools/gen-docs/SKILL.md +6 -4
- package/skills/tools/gen-docs/scripts/doc_generator.js +349 -0
- package/skills/tools/verify-change/SKILL.md +8 -6
- package/skills/tools/verify-change/scripts/change_analyzer.js +270 -0
- package/skills/tools/verify-module/SKILL.md +6 -4
- package/skills/tools/verify-module/scripts/module_scanner.js +145 -0
- package/skills/tools/verify-quality/SKILL.md +5 -3
- package/skills/tools/verify-quality/scripts/quality_checker.js +276 -0
- package/skills/tools/verify-security/SKILL.md +7 -5
- package/skills/tools/verify-security/scripts/security_scanner.js +133 -0
- package/skills/__pycache__/run_skill.cpython-312.pyc +0 -0
- package/skills/domains/COVERAGE_PLAN.md +0 -232
- package/skills/domains/ai/model-evaluation.md +0 -790
- package/skills/domains/ai/prompt-engineering.md +0 -703
- package/skills/domains/architecture/compliance.md +0 -299
- package/skills/domains/architecture/data-security.md +0 -184
- package/skills/domains/data-engineering/data-pipeline.md +0 -762
- package/skills/domains/data-engineering/data-quality.md +0 -894
- package/skills/domains/data-engineering/stream-processing.md +0 -791
- package/skills/domains/development/dart.md +0 -963
- package/skills/domains/development/kotlin.md +0 -834
- package/skills/domains/development/php.md +0 -659
- package/skills/domains/development/swift.md +0 -755
- package/skills/domains/devops/e2e-testing.md +0 -914
- package/skills/domains/devops/performance-testing.md +0 -734
- package/skills/domains/devops/testing-strategy.md +0 -667
- package/skills/domains/frontend-design/build-tools.md +0 -743
- package/skills/domains/frontend-design/performance.md +0 -734
- package/skills/domains/frontend-design/testing.md +0 -699
- package/skills/domains/infrastructure/gitops.md +0 -735
- package/skills/domains/infrastructure/iac.md +0 -855
- package/skills/domains/infrastructure/kubernetes.md +0 -1018
- package/skills/domains/mobile/android-dev.md +0 -979
- package/skills/domains/mobile/cross-platform.md +0 -795
- package/skills/domains/mobile/ios-dev.md +0 -931
- package/skills/domains/security/secrets-management.md +0 -834
- package/skills/domains/security/supply-chain.md +0 -931
- package/skills/domains/security/threat-modeling.md +0 -828
- package/skills/run_skill.py +0 -153
- package/skills/tests/README.md +0 -225
- package/skills/tests/SUMMARY.md +0 -362
- package/skills/tests/__init__.py +0 -3
- package/skills/tests/__pycache__/test_change_analyzer.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_doc_generator.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_module_scanner.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_quality_checker.cpython-312.pyc +0 -0
- package/skills/tests/__pycache__/test_security_scanner.cpython-312.pyc +0 -0
- package/skills/tests/test_change_analyzer.py +0 -558
- package/skills/tests/test_doc_generator.py +0 -538
- package/skills/tests/test_module_scanner.py +0 -376
- package/skills/tests/test_quality_checker.py +0 -516
- package/skills/tests/test_security_scanner.py +0 -426
- package/skills/tools/gen-docs/scripts/__pycache__/doc_generator.cpython-312.pyc +0 -0
- package/skills/tools/gen-docs/scripts/doc_generator.py +0 -520
- package/skills/tools/verify-change/scripts/__pycache__/change_analyzer.cpython-312.pyc +0 -0
- package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
- package/skills/tools/verify-module/scripts/__pycache__/module_scanner.cpython-312.pyc +0 -0
- package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
- package/skills/tools/verify-quality/scripts/__pycache__/quality_checker.cpython-312.pyc +0 -0
- package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
- package/skills/tools/verify-security/scripts/__pycache__/security_scanner.cpython-312.pyc +0 -0
- package/skills/tools/verify-security/scripts/security_scanner.py +0 -374
|
@@ -1,734 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: performance
|
|
3
|
-
description: 前端性能优化技术。懒加载、代码分割、虚拟滚动、Web Vitals、性能监控。当用户提到性能优化、懒加载、代码分割、虚拟滚动、LCP、FID、CLS、性能指标时使用。
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# 🎨 ⚡ 性能优化 · Performance Optimization
|
|
7
|
-
|
|
8
|
-
## 性能指标 (Core Web Vitals)
|
|
9
|
-
|
|
10
|
-
| 指标 | 含义 | 目标值 | 测量内容 |
|
|
11
|
-
|------|------|--------|----------|
|
|
12
|
-
| LCP | Largest Contentful Paint | < 2.5s | 最大内容绘制时间 |
|
|
13
|
-
| FID | First Input Delay | < 100ms | 首次输入延迟 |
|
|
14
|
-
| CLS | Cumulative Layout Shift | < 0.1 | 累积布局偏移 |
|
|
15
|
-
| FCP | First Contentful Paint | < 1.8s | 首次内容绘制 |
|
|
16
|
-
| TTI | Time to Interactive | < 3.8s | 可交互时间 |
|
|
17
|
-
| TBT | Total Blocking Time | < 200ms | 总阻塞时间 |
|
|
18
|
-
|
|
19
|
-
## 性能优化决策树
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
性能问题?
|
|
23
|
-
│
|
|
24
|
-
├─ 加载慢
|
|
25
|
-
│ ├─ Bundle 大 → 代码分割 + Tree Shaking
|
|
26
|
-
│ ├─ 资源多 → 懒加载 + 预加载
|
|
27
|
-
│ └─ 网络慢 → CDN + 压缩 + HTTP/2
|
|
28
|
-
│
|
|
29
|
-
├─ 渲染慢
|
|
30
|
-
│ ├─ 列表长 → 虚拟滚动
|
|
31
|
-
│ ├─ 重渲染 → React.memo + useMemo
|
|
32
|
-
│ └─ 布局抖动 → 固定尺寸 + CSS优化
|
|
33
|
-
│
|
|
34
|
-
└─ 交互慢
|
|
35
|
-
├─ JS 阻塞 → Web Worker + 时间切片
|
|
36
|
-
├─ 动画卡顿 → CSS动画 + requestAnimationFrame
|
|
37
|
-
└─ 事件处理 → 防抖节流 + 事件委托
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## 代码分割 (Code Splitting)
|
|
41
|
-
|
|
42
|
-
### React.lazy + Suspense
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
import { lazy, Suspense } from 'react'
|
|
46
|
-
|
|
47
|
-
// 路由级别分割
|
|
48
|
-
const Dashboard = lazy(() => import('./pages/Dashboard'))
|
|
49
|
-
const Profile = lazy(() => import('./pages/Profile'))
|
|
50
|
-
const Settings = lazy(() => import('./pages/Settings'))
|
|
51
|
-
|
|
52
|
-
function App() {
|
|
53
|
-
return (
|
|
54
|
-
<Suspense fallback={<LoadingSpinner />}>
|
|
55
|
-
<Routes>
|
|
56
|
-
<Route path="/dashboard" element={<Dashboard />} />
|
|
57
|
-
<Route path="/profile" element={<Profile />} />
|
|
58
|
-
<Route path="/settings" element={<Settings />} />
|
|
59
|
-
</Routes>
|
|
60
|
-
</Suspense>
|
|
61
|
-
)
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### 组件级别分割
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
import { lazy, Suspense } from 'react'
|
|
69
|
-
|
|
70
|
-
// 重量级组件懒加载
|
|
71
|
-
const HeavyChart = lazy(() => import('./components/HeavyChart'))
|
|
72
|
-
const RichTextEditor = lazy(() => import('./components/RichTextEditor'))
|
|
73
|
-
|
|
74
|
-
function Dashboard() {
|
|
75
|
-
const [showChart, setShowChart] = useState(false)
|
|
76
|
-
|
|
77
|
-
return (
|
|
78
|
-
<div>
|
|
79
|
-
<button onClick={() => setShowChart(true)}>Show Chart</button>
|
|
80
|
-
{showChart && (
|
|
81
|
-
<Suspense fallback={<div>Loading chart...</div>}>
|
|
82
|
-
<HeavyChart />
|
|
83
|
-
</Suspense>
|
|
84
|
-
)}
|
|
85
|
-
</div>
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### 动态导入
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
// 条件加载
|
|
94
|
-
async function loadFeature(featureName: string) {
|
|
95
|
-
if (featureName === 'analytics') {
|
|
96
|
-
const { Analytics } = await import('./features/Analytics')
|
|
97
|
-
return Analytics
|
|
98
|
-
} else if (featureName === 'reporting') {
|
|
99
|
-
const { Reporting } = await import('./features/Reporting')
|
|
100
|
-
return Reporting
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// 按需加载工具库
|
|
105
|
-
async function processData(data: any[]) {
|
|
106
|
-
const { default: _ } = await import('lodash-es')
|
|
107
|
-
return _.groupBy(data, 'category')
|
|
108
|
-
}
|
|
109
|
-
```
|
|
110
|
-
|
|
111
|
-
### Webpack 配置
|
|
112
|
-
|
|
113
|
-
```javascript
|
|
114
|
-
// webpack.config.js
|
|
115
|
-
module.exports = {
|
|
116
|
-
optimization: {
|
|
117
|
-
splitChunks: {
|
|
118
|
-
chunks: 'all',
|
|
119
|
-
cacheGroups: {
|
|
120
|
-
// 第三方库单独打包
|
|
121
|
-
vendor: {
|
|
122
|
-
test: /[\\/]node_modules[\\/]/,
|
|
123
|
-
name: 'vendors',
|
|
124
|
-
priority: 10,
|
|
125
|
-
},
|
|
126
|
-
// 公共代码提取
|
|
127
|
-
common: {
|
|
128
|
-
minChunks: 2,
|
|
129
|
-
priority: 5,
|
|
130
|
-
reuseExistingChunk: true,
|
|
131
|
-
},
|
|
132
|
-
// React 相关单独打包
|
|
133
|
-
react: {
|
|
134
|
-
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
|
|
135
|
-
name: 'react',
|
|
136
|
-
priority: 20,
|
|
137
|
-
},
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
}
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Vite 配置
|
|
145
|
-
|
|
146
|
-
```typescript
|
|
147
|
-
// vite.config.ts
|
|
148
|
-
import { defineConfig } from 'vite'
|
|
149
|
-
|
|
150
|
-
export default defineConfig({
|
|
151
|
-
build: {
|
|
152
|
-
rollupOptions: {
|
|
153
|
-
output: {
|
|
154
|
-
manualChunks: {
|
|
155
|
-
'react-vendor': ['react', 'react-dom'],
|
|
156
|
-
'router': ['react-router-dom'],
|
|
157
|
-
'ui': ['@mui/material', '@emotion/react'],
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
chunkSizeWarningLimit: 1000,
|
|
162
|
-
},
|
|
163
|
-
})
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## 懒加载 (Lazy Loading)
|
|
167
|
-
|
|
168
|
-
### 图片懒加载
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
// 原生 loading 属性
|
|
172
|
-
function ImageGallery({ images }: { images: string[] }) {
|
|
173
|
-
return (
|
|
174
|
-
<div>
|
|
175
|
-
{images.map((src, i) => (
|
|
176
|
-
<img
|
|
177
|
-
key={i}
|
|
178
|
-
src={src}
|
|
179
|
-
loading="lazy"
|
|
180
|
-
alt={`Image ${i}`}
|
|
181
|
-
width="400"
|
|
182
|
-
height="300"
|
|
183
|
-
/>
|
|
184
|
-
))}
|
|
185
|
-
</div>
|
|
186
|
-
)
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Intersection Observer
|
|
190
|
-
import { useEffect, useRef, useState } from 'react'
|
|
191
|
-
|
|
192
|
-
function LazyImage({ src, alt }: { src: string; alt: string }) {
|
|
193
|
-
const [isLoaded, setIsLoaded] = useState(false)
|
|
194
|
-
const imgRef = useRef<HTMLImageElement>(null)
|
|
195
|
-
|
|
196
|
-
useEffect(() => {
|
|
197
|
-
const observer = new IntersectionObserver(
|
|
198
|
-
([entry]) => {
|
|
199
|
-
if (entry.isIntersecting) {
|
|
200
|
-
setIsLoaded(true)
|
|
201
|
-
observer.disconnect()
|
|
202
|
-
}
|
|
203
|
-
},
|
|
204
|
-
{ rootMargin: '50px' }
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
if (imgRef.current) {
|
|
208
|
-
observer.observe(imgRef.current)
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
return () => observer.disconnect()
|
|
212
|
-
}, [])
|
|
213
|
-
|
|
214
|
-
return (
|
|
215
|
-
<img
|
|
216
|
-
ref={imgRef}
|
|
217
|
-
src={isLoaded ? src : '/placeholder.jpg'}
|
|
218
|
-
alt={alt}
|
|
219
|
-
className={isLoaded ? 'loaded' : 'loading'}
|
|
220
|
-
/>
|
|
221
|
-
)
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
### 路由预加载
|
|
226
|
-
|
|
227
|
-
```typescript
|
|
228
|
-
import { useEffect } from 'react'
|
|
229
|
-
import { useLocation } from 'react-router-dom'
|
|
230
|
-
|
|
231
|
-
// 预加载下一个可能的路由
|
|
232
|
-
function useRoutePreload() {
|
|
233
|
-
const location = useLocation()
|
|
234
|
-
|
|
235
|
-
useEffect(() => {
|
|
236
|
-
if (location.pathname === '/dashboard') {
|
|
237
|
-
// 预加载 Profile 页面
|
|
238
|
-
import('./pages/Profile')
|
|
239
|
-
} else if (location.pathname === '/profile') {
|
|
240
|
-
// 预加载 Settings 页面
|
|
241
|
-
import('./pages/Settings')
|
|
242
|
-
}
|
|
243
|
-
}, [location])
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
// Link 悬停预加载
|
|
247
|
-
function PreloadLink({ to, children }: { to: string; children: ReactNode }) {
|
|
248
|
-
const handleMouseEnter = () => {
|
|
249
|
-
if (to === '/dashboard') {
|
|
250
|
-
import('./pages/Dashboard')
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
return (
|
|
255
|
-
<Link to={to} onMouseEnter={handleMouseEnter}>
|
|
256
|
-
{children}
|
|
257
|
-
</Link>
|
|
258
|
-
)
|
|
259
|
-
}
|
|
260
|
-
```
|
|
261
|
-
|
|
262
|
-
## 虚拟滚动 (Virtual Scrolling)
|
|
263
|
-
|
|
264
|
-
### react-window
|
|
265
|
-
|
|
266
|
-
```typescript
|
|
267
|
-
import { FixedSizeList } from 'react-window'
|
|
268
|
-
|
|
269
|
-
interface Item {
|
|
270
|
-
id: string
|
|
271
|
-
name: string
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function VirtualList({ items }: { items: Item[] }) {
|
|
275
|
-
const Row = ({ index, style }: { index: number; style: CSSProperties }) => (
|
|
276
|
-
<div style={style}>
|
|
277
|
-
{items[index].name}
|
|
278
|
-
</div>
|
|
279
|
-
)
|
|
280
|
-
|
|
281
|
-
return (
|
|
282
|
-
<FixedSizeList
|
|
283
|
-
height={600}
|
|
284
|
-
itemCount={items.length}
|
|
285
|
-
itemSize={50}
|
|
286
|
-
width="100%"
|
|
287
|
-
>
|
|
288
|
-
{Row}
|
|
289
|
-
</FixedSizeList>
|
|
290
|
-
)
|
|
291
|
-
}
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
### 动态高度列表
|
|
295
|
-
|
|
296
|
-
```typescript
|
|
297
|
-
import { VariableSizeList } from 'react-window'
|
|
298
|
-
|
|
299
|
-
function DynamicList({ items }: { items: Item[] }) {
|
|
300
|
-
const listRef = useRef<VariableSizeList>(null)
|
|
301
|
-
|
|
302
|
-
// 计算每项高度
|
|
303
|
-
const getItemSize = (index: number) => {
|
|
304
|
-
return items[index].content.length > 100 ? 120 : 60
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
const Row = ({ index, style }: { index: number; style: CSSProperties }) => (
|
|
308
|
-
<div style={style}>
|
|
309
|
-
<h3>{items[index].title}</h3>
|
|
310
|
-
<p>{items[index].content}</p>
|
|
311
|
-
</div>
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
return (
|
|
315
|
-
<VariableSizeList
|
|
316
|
-
ref={listRef}
|
|
317
|
-
height={600}
|
|
318
|
-
itemCount={items.length}
|
|
319
|
-
itemSize={getItemSize}
|
|
320
|
-
width="100%"
|
|
321
|
-
>
|
|
322
|
-
{Row}
|
|
323
|
-
</VariableSizeList>
|
|
324
|
-
)
|
|
325
|
-
}
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
### 虚拟网格
|
|
329
|
-
|
|
330
|
-
```typescript
|
|
331
|
-
import { FixedSizeGrid } from 'react-window'
|
|
332
|
-
|
|
333
|
-
function VirtualGrid({ items }: { items: Item[] }) {
|
|
334
|
-
const COLUMN_COUNT = 4
|
|
335
|
-
const ROW_COUNT = Math.ceil(items.length / COLUMN_COUNT)
|
|
336
|
-
|
|
337
|
-
const Cell = ({ columnIndex, rowIndex, style }: any) => {
|
|
338
|
-
const index = rowIndex * COLUMN_COUNT + columnIndex
|
|
339
|
-
if (index >= items.length) return null
|
|
340
|
-
|
|
341
|
-
return (
|
|
342
|
-
<div style={style}>
|
|
343
|
-
<img src={items[index].thumbnail} alt={items[index].name} />
|
|
344
|
-
<p>{items[index].name}</p>
|
|
345
|
-
</div>
|
|
346
|
-
)
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
return (
|
|
350
|
-
<FixedSizeGrid
|
|
351
|
-
columnCount={COLUMN_COUNT}
|
|
352
|
-
columnWidth={200}
|
|
353
|
-
height={600}
|
|
354
|
-
rowCount={ROW_COUNT}
|
|
355
|
-
rowHeight={200}
|
|
356
|
-
width={800}
|
|
357
|
-
>
|
|
358
|
-
{Cell}
|
|
359
|
-
</FixedSizeGrid>
|
|
360
|
-
)
|
|
361
|
-
}
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### 自定义虚拟滚动
|
|
365
|
-
|
|
366
|
-
```typescript
|
|
367
|
-
import { useState, useEffect, useRef } from 'react'
|
|
368
|
-
|
|
369
|
-
function useVirtualScroll<T>(
|
|
370
|
-
items: T[],
|
|
371
|
-
itemHeight: number,
|
|
372
|
-
containerHeight: number
|
|
373
|
-
) {
|
|
374
|
-
const [scrollTop, setScrollTop] = useState(0)
|
|
375
|
-
|
|
376
|
-
const startIndex = Math.floor(scrollTop / itemHeight)
|
|
377
|
-
const endIndex = Math.ceil((scrollTop + containerHeight) / itemHeight)
|
|
378
|
-
const visibleItems = items.slice(startIndex, endIndex + 1)
|
|
379
|
-
|
|
380
|
-
const totalHeight = items.length * itemHeight
|
|
381
|
-
const offsetY = startIndex * itemHeight
|
|
382
|
-
|
|
383
|
-
return {
|
|
384
|
-
visibleItems,
|
|
385
|
-
totalHeight,
|
|
386
|
-
offsetY,
|
|
387
|
-
onScroll: (e: React.UIEvent<HTMLDivElement>) => {
|
|
388
|
-
setScrollTop(e.currentTarget.scrollTop)
|
|
389
|
-
},
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
function CustomVirtualList({ items }: { items: Item[] }) {
|
|
394
|
-
const { visibleItems, totalHeight, offsetY, onScroll } = useVirtualScroll(
|
|
395
|
-
items,
|
|
396
|
-
50,
|
|
397
|
-
600
|
|
398
|
-
)
|
|
399
|
-
|
|
400
|
-
return (
|
|
401
|
-
<div style={{ height: 600, overflow: 'auto' }} onScroll={onScroll}>
|
|
402
|
-
<div style={{ height: totalHeight, position: 'relative' }}>
|
|
403
|
-
<div style={{ transform: `translateY(${offsetY}px)` }}>
|
|
404
|
-
{visibleItems.map((item) => (
|
|
405
|
-
<div key={item.id} style={{ height: 50 }}>
|
|
406
|
-
{item.name}
|
|
407
|
-
</div>
|
|
408
|
-
))}
|
|
409
|
-
</div>
|
|
410
|
-
</div>
|
|
411
|
-
</div>
|
|
412
|
-
)
|
|
413
|
-
}
|
|
414
|
-
```
|
|
415
|
-
|
|
416
|
-
## React 性能优化
|
|
417
|
-
|
|
418
|
-
### React.memo
|
|
419
|
-
|
|
420
|
-
```typescript
|
|
421
|
-
import { memo } from 'react'
|
|
422
|
-
|
|
423
|
-
// 避免不必要的重渲染
|
|
424
|
-
const ExpensiveComponent = memo(function ExpensiveComponent({
|
|
425
|
-
data,
|
|
426
|
-
onUpdate,
|
|
427
|
-
}: {
|
|
428
|
-
data: Data
|
|
429
|
-
onUpdate: (id: string) => void
|
|
430
|
-
}) {
|
|
431
|
-
return <div>{/* 复杂渲染逻辑 */}</div>
|
|
432
|
-
})
|
|
433
|
-
|
|
434
|
-
// 自定义比较函数
|
|
435
|
-
const CustomMemo = memo(
|
|
436
|
-
function Component({ user }: { user: User }) {
|
|
437
|
-
return <div>{user.name}</div>
|
|
438
|
-
},
|
|
439
|
-
(prevProps, nextProps) => {
|
|
440
|
-
// 只在 user.id 变化时重渲染
|
|
441
|
-
return prevProps.user.id === nextProps.user.id
|
|
442
|
-
}
|
|
443
|
-
)
|
|
444
|
-
```
|
|
445
|
-
|
|
446
|
-
### useMemo + useCallback
|
|
447
|
-
|
|
448
|
-
```typescript
|
|
449
|
-
import { useMemo, useCallback } from 'react'
|
|
450
|
-
|
|
451
|
-
function DataTable({ data, filter }: { data: Item[]; filter: string }) {
|
|
452
|
-
// 缓存计算结果
|
|
453
|
-
const filteredData = useMemo(() => {
|
|
454
|
-
return data.filter((item) => item.name.includes(filter))
|
|
455
|
-
}, [data, filter])
|
|
456
|
-
|
|
457
|
-
const sortedData = useMemo(() => {
|
|
458
|
-
return [...filteredData].sort((a, b) => a.name.localeCompare(b.name))
|
|
459
|
-
}, [filteredData])
|
|
460
|
-
|
|
461
|
-
// 缓存回调函数
|
|
462
|
-
const handleClick = useCallback(
|
|
463
|
-
(id: string) => {
|
|
464
|
-
console.log('Clicked:', id)
|
|
465
|
-
},
|
|
466
|
-
[]
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
return (
|
|
470
|
-
<div>
|
|
471
|
-
{sortedData.map((item) => (
|
|
472
|
-
<Row key={item.id} item={item} onClick={handleClick} />
|
|
473
|
-
))}
|
|
474
|
-
</div>
|
|
475
|
-
)
|
|
476
|
-
}
|
|
477
|
-
```
|
|
478
|
-
|
|
479
|
-
### 状态批量更新
|
|
480
|
-
|
|
481
|
-
```typescript
|
|
482
|
-
import { unstable_batchedUpdates } from 'react-dom'
|
|
483
|
-
|
|
484
|
-
// React 18 自动批处理
|
|
485
|
-
function Component() {
|
|
486
|
-
const [count, setCount] = useState(0)
|
|
487
|
-
const [flag, setFlag] = useState(false)
|
|
488
|
-
|
|
489
|
-
const handleClick = () => {
|
|
490
|
-
// React 18 中自动批处理,只触发一次渲染
|
|
491
|
-
setCount((c) => c + 1)
|
|
492
|
-
setFlag((f) => !f)
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// React 17 需要手动批处理
|
|
496
|
-
const handleClickLegacy = () => {
|
|
497
|
-
unstable_batchedUpdates(() => {
|
|
498
|
-
setCount((c) => c + 1)
|
|
499
|
-
setFlag((f) => !f)
|
|
500
|
-
})
|
|
501
|
-
}
|
|
502
|
-
}
|
|
503
|
-
```
|
|
504
|
-
|
|
505
|
-
### 时间切片
|
|
506
|
-
|
|
507
|
-
```typescript
|
|
508
|
-
// 使用 startTransition 标记低优先级更新
|
|
509
|
-
import { startTransition, useState } from 'react'
|
|
510
|
-
|
|
511
|
-
function SearchResults() {
|
|
512
|
-
const [query, setQuery] = useState('')
|
|
513
|
-
const [results, setResults] = useState<string[]>([])
|
|
514
|
-
|
|
515
|
-
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
516
|
-
// 高优先级:立即更新输入框
|
|
517
|
-
setQuery(e.target.value)
|
|
518
|
-
|
|
519
|
-
// 低优先级:延迟更新搜索结果
|
|
520
|
-
startTransition(() => {
|
|
521
|
-
const filtered = heavySearch(e.target.value)
|
|
522
|
-
setResults(filtered)
|
|
523
|
-
})
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
return (
|
|
527
|
-
<div>
|
|
528
|
-
<input value={query} onChange={handleChange} />
|
|
529
|
-
<ul>
|
|
530
|
-
{results.map((r) => (
|
|
531
|
-
<li key={r}>{r}</li>
|
|
532
|
-
))}
|
|
533
|
-
</ul>
|
|
534
|
-
</div>
|
|
535
|
-
)
|
|
536
|
-
}
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
## 资源优化
|
|
540
|
-
|
|
541
|
-
### 图片优化
|
|
542
|
-
|
|
543
|
-
```typescript
|
|
544
|
-
// Next.js Image 组件
|
|
545
|
-
import Image from 'next/image'
|
|
546
|
-
|
|
547
|
-
function OptimizedImage() {
|
|
548
|
-
return (
|
|
549
|
-
<Image
|
|
550
|
-
src="/hero.jpg"
|
|
551
|
-
alt="Hero"
|
|
552
|
-
width={1200}
|
|
553
|
-
height={600}
|
|
554
|
-
priority // LCP 图片优先加载
|
|
555
|
-
placeholder="blur"
|
|
556
|
-
blurDataURL="data:image/jpeg;base64,..."
|
|
557
|
-
/>
|
|
558
|
-
)
|
|
559
|
-
}
|
|
560
|
-
|
|
561
|
-
// 响应式图片
|
|
562
|
-
function ResponsiveImage() {
|
|
563
|
-
return (
|
|
564
|
-
<picture>
|
|
565
|
-
<source
|
|
566
|
-
srcSet="/hero.webp"
|
|
567
|
-
type="image/webp"
|
|
568
|
-
media="(min-width: 768px)"
|
|
569
|
-
/>
|
|
570
|
-
<source srcSet="/hero-mobile.jpg" media="(max-width: 767px)" />
|
|
571
|
-
<img src="/hero.jpg" alt="Hero" loading="lazy" />
|
|
572
|
-
</picture>
|
|
573
|
-
)
|
|
574
|
-
}
|
|
575
|
-
```
|
|
576
|
-
|
|
577
|
-
### 字体优化
|
|
578
|
-
|
|
579
|
-
```css
|
|
580
|
-
/* 字体预加载 */
|
|
581
|
-
<link
|
|
582
|
-
rel="preload"
|
|
583
|
-
href="/fonts/inter.woff2"
|
|
584
|
-
as="font"
|
|
585
|
-
type="font/woff2"
|
|
586
|
-
crossorigin
|
|
587
|
-
/>
|
|
588
|
-
|
|
589
|
-
/* font-display 策略 */
|
|
590
|
-
@font-face {
|
|
591
|
-
font-family: 'Inter';
|
|
592
|
-
src: url('/fonts/inter.woff2') format('woff2');
|
|
593
|
-
font-display: swap; /* 立即显示备用字体 */
|
|
594
|
-
font-weight: 400;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/* 可变字体 */
|
|
598
|
-
@font-face {
|
|
599
|
-
font-family: 'Inter Variable';
|
|
600
|
-
src: url('/fonts/inter-variable.woff2') format('woff2-variations');
|
|
601
|
-
font-weight: 100 900;
|
|
602
|
-
}
|
|
603
|
-
```
|
|
604
|
-
|
|
605
|
-
### 预加载策略
|
|
606
|
-
|
|
607
|
-
```html
|
|
608
|
-
<!-- DNS 预解析 -->
|
|
609
|
-
<link rel="dns-prefetch" href="https://api.example.com" />
|
|
610
|
-
|
|
611
|
-
<!-- 预连接 -->
|
|
612
|
-
<link rel="preconnect" href="https://cdn.example.com" />
|
|
613
|
-
|
|
614
|
-
<!-- 预加载关键资源 -->
|
|
615
|
-
<link rel="preload" href="/critical.css" as="style" />
|
|
616
|
-
<link rel="preload" href="/hero.jpg" as="image" />
|
|
617
|
-
|
|
618
|
-
<!-- 预获取下一页资源 -->
|
|
619
|
-
<link rel="prefetch" href="/next-page.js" />
|
|
620
|
-
|
|
621
|
-
<!-- 预渲染下一页 -->
|
|
622
|
-
<link rel="prerender" href="/next-page" />
|
|
623
|
-
```
|
|
624
|
-
|
|
625
|
-
## 性能监控
|
|
626
|
-
|
|
627
|
-
### Web Vitals 测量
|
|
628
|
-
|
|
629
|
-
```typescript
|
|
630
|
-
import { onCLS, onFID, onLCP, onFCP, onTTFB } from 'web-vitals'
|
|
631
|
-
|
|
632
|
-
function sendToAnalytics(metric: Metric) {
|
|
633
|
-
const body = JSON.stringify(metric)
|
|
634
|
-
const url = '/api/analytics'
|
|
635
|
-
|
|
636
|
-
// 使用 sendBeacon 确保数据发送
|
|
637
|
-
if (navigator.sendBeacon) {
|
|
638
|
-
navigator.sendBeacon(url, body)
|
|
639
|
-
} else {
|
|
640
|
-
fetch(url, { body, method: 'POST', keepalive: true })
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// 监控所有指标
|
|
645
|
-
onCLS(sendToAnalytics)
|
|
646
|
-
onFID(sendToAnalytics)
|
|
647
|
-
onLCP(sendToAnalytics)
|
|
648
|
-
onFCP(sendToAnalytics)
|
|
649
|
-
onTTFB(sendToAnalytics)
|
|
650
|
-
```
|
|
651
|
-
|
|
652
|
-
### Performance API
|
|
653
|
-
|
|
654
|
-
```typescript
|
|
655
|
-
// 测量自定义指标
|
|
656
|
-
function measureCustomMetric() {
|
|
657
|
-
performance.mark('feature-start')
|
|
658
|
-
|
|
659
|
-
// 执行操作
|
|
660
|
-
doSomething()
|
|
661
|
-
|
|
662
|
-
performance.mark('feature-end')
|
|
663
|
-
performance.measure('feature-duration', 'feature-start', 'feature-end')
|
|
664
|
-
|
|
665
|
-
const measure = performance.getEntriesByName('feature-duration')[0]
|
|
666
|
-
console.log('Duration:', measure.duration)
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
// 监控资源加载
|
|
670
|
-
const observer = new PerformanceObserver((list) => {
|
|
671
|
-
for (const entry of list.getEntries()) {
|
|
672
|
-
if (entry.entryType === 'resource') {
|
|
673
|
-
console.log(`${entry.name}: ${entry.duration}ms`)
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
})
|
|
677
|
-
|
|
678
|
-
observer.observe({ entryTypes: ['resource', 'navigation'] })
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
### React DevTools Profiler
|
|
682
|
-
|
|
683
|
-
```typescript
|
|
684
|
-
import { Profiler } from 'react'
|
|
685
|
-
|
|
686
|
-
function onRenderCallback(
|
|
687
|
-
id: string,
|
|
688
|
-
phase: 'mount' | 'update',
|
|
689
|
-
actualDuration: number,
|
|
690
|
-
baseDuration: number,
|
|
691
|
-
startTime: number,
|
|
692
|
-
commitTime: number
|
|
693
|
-
) {
|
|
694
|
-
console.log(`${id} (${phase}) took ${actualDuration}ms`)
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
function App() {
|
|
698
|
-
return (
|
|
699
|
-
<Profiler id="App" onRender={onRenderCallback}>
|
|
700
|
-
<Dashboard />
|
|
701
|
-
</Profiler>
|
|
702
|
-
)
|
|
703
|
-
}
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
## 最佳实践清单
|
|
707
|
-
|
|
708
|
-
- ✅ 使用代码分割减小初始 bundle 大小
|
|
709
|
-
- ✅ 懒加载非关键资源和路由
|
|
710
|
-
- ✅ 虚拟滚动处理长列表
|
|
711
|
-
- ✅ 使用 React.memo 避免不必要的重渲染
|
|
712
|
-
- ✅ useMemo/useCallback 缓存计算和回调
|
|
713
|
-
- ✅ 图片使用 WebP 格式 + 懒加载
|
|
714
|
-
- ✅ 字体使用 font-display: swap
|
|
715
|
-
- ✅ 预加载关键资源
|
|
716
|
-
- ✅ 监控 Core Web Vitals
|
|
717
|
-
- ✅ 使用 CDN 加速静态资源
|
|
718
|
-
- ✅ 启用 Gzip/Brotli 压缩
|
|
719
|
-
- ✅ 实施 HTTP/2 或 HTTP/3
|
|
720
|
-
|
|
721
|
-
## 工具清单
|
|
722
|
-
|
|
723
|
-
| 工具 | 用途 |
|
|
724
|
-
|------|------|
|
|
725
|
-
| Lighthouse | 性能审计 |
|
|
726
|
-
| WebPageTest | 详细性能分析 |
|
|
727
|
-
| Chrome DevTools | 性能分析和调试 |
|
|
728
|
-
| React DevTools Profiler | React 性能分析 |
|
|
729
|
-
| webpack-bundle-analyzer | Bundle 分析 |
|
|
730
|
-
| web-vitals | Core Web Vitals 监控 |
|
|
731
|
-
| react-window | 虚拟滚动 |
|
|
732
|
-
| Sentry | 性能监控 |
|
|
733
|
-
|
|
734
|
-
---
|