docs: ADR-042 前端效能優化模式 (DOM Bypass + Optimistic Updates)
All checks were successful
E2E Health Check / e2e-health (push) Successful in 16s
All checks were successful
E2E Health Check / e2e-health (push) Successful in 16s
新增 ADR-042: - Pattern 1: DOM Bypass (繞過 React 渲染,100x 效能提升) - Pattern 2: Optimistic Updates (0ms UI 延遲 + 失敗回滾) - Pattern 3: SSE Incremental Updates (增量更新,減少 API 請求) - Pattern 4: AbortController (防止記憶體洩漏) 更新 Skills 01: - v1.6 版本更新 - 新增效能優化模式章節 - 參考 ADR-042 首席架構師審查: 96-98/100 OUTSTANDING Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,10 +10,10 @@
|
||||
|
||||
| 欄位 | 值 |
|
||||
|------|-----|
|
||||
| **版本** | v1.4 |
|
||||
| **版本** | v1.6 |
|
||||
| **建立日期** | 2026-03-20 (台北) |
|
||||
| **建立者** | Claude Code |
|
||||
| **最後修改** | 2026-03-28 19:00 (台北) |
|
||||
| **最後修改** | 2026-03-31 (台北) |
|
||||
| **修改者** | Claude Code (首席架構師) |
|
||||
|
||||
### 變更紀錄
|
||||
@@ -26,6 +26,7 @@
|
||||
| v1.3 | 2026-03-27 | Claude Code | Phase 19 Z-Index/GenUI/快捷鍵規範 |
|
||||
| v1.4 | 2026-03-28 | Claude Code | ✅ Phase 19 Wave 0-5 完成 (~95% + Telemetry 整合) |
|
||||
| v1.5 | 2026-03-30 | Claude Code | 🔴🔴🔴 前端建置禁止內網 IP (瀏覽器權限事故) |
|
||||
| v1.6 | 2026-03-31 | Claude Code | 🚀 ADR-042 效能優化模式 (DOM Bypass + Optimistic Updates) |
|
||||
|
||||
---
|
||||
|
||||
@@ -285,11 +286,78 @@ document.addEventListener('keydown', ...) // 違規!
|
||||
|
||||
---
|
||||
|
||||
## 🚀 效能優化模式 (ADR-042)
|
||||
|
||||
> **參考**: [ADR-042-frontend-performance-patterns.md](../../docs/adr/ADR-042-frontend-performance-patterns.md)
|
||||
|
||||
### Pattern 1: DOM Bypass (繞過 React 渲染)
|
||||
|
||||
**適用**: 高頻更新 (>10/sec)、大量 DOM 節點 (>100 items)
|
||||
|
||||
```typescript
|
||||
// ✅ 正確: 直接操作 DOM,不經過 React state
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
function createLineElement(line: StreamLine): HTMLDivElement {
|
||||
const div = document.createElement('div')
|
||||
div.textContent = line.content // XSS 安全
|
||||
return div
|
||||
}
|
||||
|
||||
// SSE 直接 append,不用 setState
|
||||
containerRef.current.appendChild(createLineElement(data))
|
||||
enforceMaxLines(containerRef.current, 500) // 記憶體安全
|
||||
```
|
||||
|
||||
### Pattern 2: Optimistic Updates (樂觀更新)
|
||||
|
||||
**適用**: 用戶觸發狀態變更、需即時回饋
|
||||
|
||||
```typescript
|
||||
// ✅ 正確: 保存原始 → 樂觀更新 → API → 失敗回滾
|
||||
const original = [...state.items]
|
||||
set({ items: updatedItems }) // 0ms UI 回饋
|
||||
try {
|
||||
await api.update(id)
|
||||
} catch {
|
||||
set({ items: original }) // 回滾
|
||||
}
|
||||
```
|
||||
|
||||
### Pattern 3: AbortController (請求取消)
|
||||
|
||||
**適用**: 組件 unmount、新請求覆蓋舊請求
|
||||
|
||||
```typescript
|
||||
// ✅ 正確: 每次請求前取消前一次
|
||||
const abortRef = useRef<AbortController | null>(null)
|
||||
|
||||
const fetchData = async () => {
|
||||
abortRef.current?.abort()
|
||||
abortRef.current = new AbortController()
|
||||
await fetch(url, { signal: abortRef.current.signal })
|
||||
}
|
||||
|
||||
useEffect(() => () => abortRef.current?.abort(), []) // cleanup
|
||||
```
|
||||
|
||||
### Pattern 4: Exponential Backoff (指數退避)
|
||||
|
||||
**適用**: SSE 重連、API 重試
|
||||
|
||||
```typescript
|
||||
const delay = Math.min(BASE * Math.pow(2, attempts), MAX_DELAY)
|
||||
setTimeout(() => reconnect(), delay)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 參考文檔
|
||||
|
||||
- ADR-002: Nothing.tech 設計系統
|
||||
- ADR-031: Omni-Terminal SSE 架構
|
||||
- ADR-032: GenUI 動態渲染機制
|
||||
- ADR-042: 前端效能優化模式
|
||||
- `apps/web/tailwind.config.ts`: 顏色定義
|
||||
- `apps/web/src/components/ui/`: 原子組件庫
|
||||
- `apps/web/src/components/genui/`: GenUI 卡片
|
||||
|
||||
Reference in New Issue
Block a user