@xaviele/ag-kit 1.0.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/README.md +20 -0
- package/bin/cli.js +63 -0
- package/package.json +27 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/template/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/template/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/core.cpython-313.pyc +0 -0
- package/template/.agent/.shared/ui-ux-pro-max/scripts/__pycache__/design_system.cpython-313.pyc +0 -0
- package/template/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/template/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/template/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/template/.agent/ARCHITECTURE.md +281 -0
- package/template/.agent/agents/backend-specialist.md +263 -0
- package/template/.agent/agents/code-archaeologist.md +106 -0
- package/template/.agent/agents/database-architect.md +226 -0
- package/template/.agent/agents/debugger.md +225 -0
- package/template/.agent/agents/devops-engineer.md +242 -0
- package/template/.agent/agents/documentation-writer.md +104 -0
- package/template/.agent/agents/explorer-agent.md +73 -0
- package/template/.agent/agents/frontend-specialist.md +593 -0
- package/template/.agent/agents/game-developer.md +162 -0
- package/template/.agent/agents/mobile-developer.md +377 -0
- package/template/.agent/agents/orchestrator.md +416 -0
- package/template/.agent/agents/penetration-tester.md +188 -0
- package/template/.agent/agents/performance-optimizer.md +187 -0
- package/template/.agent/agents/product-manager.md +112 -0
- package/template/.agent/agents/product-owner.md +95 -0
- package/template/.agent/agents/project-planner.md +406 -0
- package/template/.agent/agents/qa-automation-engineer.md +103 -0
- package/template/.agent/agents/security-auditor.md +170 -0
- package/template/.agent/agents/seo-specialist.md +111 -0
- package/template/.agent/agents/test-engineer.md +158 -0
- package/template/.agent/mcp_config.json +24 -0
- package/template/.agent/rules/GEMINI.md +273 -0
- package/template/.agent/scripts/auto_preview.py +148 -0
- package/template/.agent/scripts/checklist.py +217 -0
- package/template/.agent/scripts/session_manager.py +120 -0
- package/template/.agent/scripts/verify_all.py +327 -0
- package/template/.agent/skills/adr/SKILL.md +282 -0
- package/template/.agent/skills/alirezarezvani-code-to-prd/SKILL.md +499 -0
- package/template/.agent/skills/api-patterns/SKILL.md +81 -0
- package/template/.agent/skills/api-patterns/api-style.md +42 -0
- package/template/.agent/skills/api-patterns/auth.md +24 -0
- package/template/.agent/skills/api-patterns/documentation.md +26 -0
- package/template/.agent/skills/api-patterns/graphql.md +41 -0
- package/template/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/template/.agent/skills/api-patterns/response.md +37 -0
- package/template/.agent/skills/api-patterns/rest.md +40 -0
- package/template/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/template/.agent/skills/api-patterns/security-testing.md +122 -0
- package/template/.agent/skills/api-patterns/trpc.md +41 -0
- package/template/.agent/skills/api-patterns/versioning.md +22 -0
- package/template/.agent/skills/app-builder/SKILL.md +75 -0
- package/template/.agent/skills/app-builder/agent-coordination.md +71 -0
- package/template/.agent/skills/app-builder/feature-building.md +53 -0
- package/template/.agent/skills/app-builder/project-detection.md +34 -0
- package/template/.agent/skills/app-builder/scaffolding.md +118 -0
- package/template/.agent/skills/app-builder/tech-stack.md +41 -0
- package/template/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/template/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/template/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/template/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/template/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/template/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/template/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/template/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/template/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/template/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/template/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/template/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/template/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/template/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/template/.agent/skills/architecture/SKILL.md +55 -0
- package/template/.agent/skills/architecture/context-discovery.md +43 -0
- package/template/.agent/skills/architecture/examples.md +94 -0
- package/template/.agent/skills/architecture/pattern-selection.md +68 -0
- package/template/.agent/skills/architecture/patterns-reference.md +50 -0
- package/template/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/template/.agent/skills/bash-linux/SKILL.md +199 -0
- package/template/.agent/skills/behavioral-modes/SKILL.md +242 -0
- package/template/.agent/skills/brainstorming/SKILL.md +163 -0
- package/template/.agent/skills/brainstorming/dynamic-questioning.md +350 -0
- package/template/.agent/skills/claudekit-ai-multimodal/SKILL.md +353 -0
- package/template/.agent/skills/clean-code/SKILL.md +201 -0
- package/template/.agent/skills/code-review-checklist/SKILL.md +109 -0
- package/template/.agent/skills/database-design/SKILL.md +52 -0
- package/template/.agent/skills/database-design/database-selection.md +43 -0
- package/template/.agent/skills/database-design/indexing.md +39 -0
- package/template/.agent/skills/database-design/migrations.md +48 -0
- package/template/.agent/skills/database-design/optimization.md +36 -0
- package/template/.agent/skills/database-design/orm-selection.md +30 -0
- package/template/.agent/skills/database-design/schema-design.md +56 -0
- package/template/.agent/skills/database-design/scripts/schema_validator.py +172 -0
- package/template/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/template/.agent/skills/doc.md +177 -0
- package/template/.agent/skills/document/SKILL.md +250 -0
- package/template/.agent/skills/documentation-templates/SKILL.md +194 -0
- package/template/.agent/skills/frontend-design/SKILL.md +452 -0
- package/template/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/template/.agent/skills/frontend-design/color-system.md +311 -0
- package/template/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/template/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/template/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/template/.agent/skills/frontend-design/scripts/ux_audit.py +722 -0
- package/template/.agent/skills/frontend-design/typography-system.md +345 -0
- package/template/.agent/skills/frontend-design/ux-psychology.md +1116 -0
- package/template/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/template/.agent/skills/game-development/2d-games/SKILL.md +119 -0
- package/template/.agent/skills/game-development/3d-games/SKILL.md +135 -0
- package/template/.agent/skills/game-development/SKILL.md +167 -0
- package/template/.agent/skills/game-development/game-art/SKILL.md +185 -0
- package/template/.agent/skills/game-development/game-audio/SKILL.md +190 -0
- package/template/.agent/skills/game-development/game-design/SKILL.md +129 -0
- package/template/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
- package/template/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
- package/template/.agent/skills/game-development/pc-games/SKILL.md +144 -0
- package/template/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
- package/template/.agent/skills/game-development/web-games/SKILL.md +150 -0
- package/template/.agent/skills/geo-fundamentals/SKILL.md +156 -0
- package/template/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/template/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/template/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/template/.agent/skills/intelligent-routing/SKILL.md +335 -0
- package/template/.agent/skills/lint-and-validate/SKILL.md +45 -0
- package/template/.agent/skills/lint-and-validate/scripts/lint_runner.py +184 -0
- package/template/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
- package/template/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/template/.agent/skills/mindrally-meta-prompt/SKILL.md +129 -0
- package/template/.agent/skills/mobile-design/SKILL.md +394 -0
- package/template/.agent/skills/mobile-design/decision-trees.md +516 -0
- package/template/.agent/skills/mobile-design/mobile-backend.md +491 -0
- package/template/.agent/skills/mobile-design/mobile-color-system.md +420 -0
- package/template/.agent/skills/mobile-design/mobile-debugging.md +122 -0
- package/template/.agent/skills/mobile-design/mobile-design-thinking.md +357 -0
- package/template/.agent/skills/mobile-design/mobile-navigation.md +458 -0
- package/template/.agent/skills/mobile-design/mobile-performance.md +767 -0
- package/template/.agent/skills/mobile-design/mobile-testing.md +356 -0
- package/template/.agent/skills/mobile-design/mobile-typography.md +433 -0
- package/template/.agent/skills/mobile-design/platform-android.md +666 -0
- package/template/.agent/skills/mobile-design/platform-ios.md +561 -0
- package/template/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/template/.agent/skills/mobile-design/touch-psychology.md +537 -0
- package/template/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +351 -0
- package/template/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +240 -0
- package/template/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +490 -0
- package/template/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +264 -0
- package/template/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
- package/template/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +432 -0
- package/template/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +684 -0
- package/template/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +150 -0
- package/template/.agent/skills/nextjs-react-expert/9-cache-components.md +103 -0
- package/template/.agent/skills/nextjs-react-expert/SKILL.md +293 -0
- package/template/.agent/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
- package/template/.agent/skills/nextjs-react-expert/scripts/react_performance_checker.py +252 -0
- package/template/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
- package/template/.agent/skills/parallel-agents/SKILL.md +175 -0
- package/template/.agent/skills/performance-profiling/SKILL.md +143 -0
- package/template/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/template/.agent/skills/plan-writing/SKILL.md +152 -0
- package/template/.agent/skills/pm-skills-create-prd/SKILL.md +88 -0
- package/template/.agent/skills/powershell-windows/SKILL.md +167 -0
- package/template/.agent/skills/prompt-engineering/SKILL.md +566 -0
- package/template/.agent/skills/python-patterns/SKILL.md +441 -0
- package/template/.agent/skills/red-team-tactics/SKILL.md +199 -0
- package/template/.agent/skills/rust-pro/SKILL.md +176 -0
- package/template/.agent/skills/seo-fundamentals/SKILL.md +129 -0
- package/template/.agent/skills/seo-fundamentals/scripts/seo_checker.py +219 -0
- package/template/.agent/skills/server-management/SKILL.md +161 -0
- package/template/.agent/skills/skills/adr/SKILL.md +282 -0
- package/template/.agent/skills/skills/alirezarezvani-code-to-prd/SKILL.md +499 -0
- package/template/.agent/skills/skills/claudekit-ai-multimodal/SKILL.md +353 -0
- package/template/.agent/skills/skills/document/SKILL.md +250 -0
- package/template/.agent/skills/skills/mindrally-meta-prompt/SKILL.md +129 -0
- package/template/.agent/skills/skills/pm-skills-create-prd/SKILL.md +88 -0
- package/template/.agent/skills/skills/prompt-engineering/SKILL.md +566 -0
- package/template/.agent/skills/systematic-debugging/SKILL.md +109 -0
- package/template/.agent/skills/tailwind-patterns/SKILL.md +269 -0
- package/template/.agent/skills/tdd-workflow/SKILL.md +149 -0
- package/template/.agent/skills/testing-patterns/SKILL.md +178 -0
- package/template/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/template/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
- package/template/.agent/skills/vulnerability-scanner/checklists.md +121 -0
- package/template/.agent/skills/vulnerability-scanner/scripts/security_scan.py +458 -0
- package/template/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/template/.agent/skills/webapp-testing/SKILL.md +187 -0
- package/template/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
- package/template/.agent/skills/zalo-mini-app/SKILL.md +81 -0
- package/template/.agent/skills/zalo-mini-app/references/api-device.md +121 -0
- package/template/.agent/skills/zalo-mini-app/references/api-overview.md +88 -0
- package/template/.agent/skills/zalo-mini-app/references/api-storage.md +74 -0
- package/template/.agent/skills/zalo-mini-app/references/api-ui.md +124 -0
- package/template/.agent/skills/zalo-mini-app/references/api-user.md +113 -0
- package/template/.agent/skills/zalo-mini-app/references/api-zalo.md +127 -0
- package/template/.agent/skills/zalo-mini-app/references/design-guidelines.md +70 -0
- package/template/.agent/skills/zalo-mini-app/references/getting-started.md +95 -0
- package/template/.agent/skills/zalo-mini-app/references/react-best-practices.md +790 -0
- package/template/.agent/skills/zalo-mini-app/references/web-design-guidelines.md +591 -0
- package/template/.agent/skills/zalo-mini-app/references/zaui-display.md +103 -0
- package/template/.agent/skills/zalo-mini-app/references/zaui-form.md +108 -0
- package/template/.agent/skills/zalo-mini-app/references/zaui-layout.md +94 -0
- package/template/.agent/skills/zalo-mini-app/references/zaui-overlay.md +98 -0
- package/template/.agent/skills/zalo-mini-app/references/zaui-overview.md +82 -0
- package/template/.agent/workflows/brainstorm.md +113 -0
- package/template/.agent/workflows/create.md +59 -0
- package/template/.agent/workflows/debug.md +103 -0
- package/template/.agent/workflows/deploy.md +176 -0
- package/template/.agent/workflows/enhance.md +63 -0
- package/template/.agent/workflows/orchestrate.md +237 -0
- package/template/.agent/workflows/plan.md +89 -0
- package/template/.agent/workflows/preview.md +81 -0
- package/template/.agent/workflows/status.md +86 -0
- package/template/.agent/workflows/test.md +144 -0
- package/template/.agent/workflows/ui-ux-pro-max.md +296 -0
- package/template/.agent/workflows/veo-marketing.md +46 -0
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
# React Best Practices
|
|
2
|
+
|
|
3
|
+
Comprehensive performance optimization guide for React applications in Zalo Mini Apps. Based on Vercel Engineering guidelines.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Eliminating Waterfalls (CRITICAL)
|
|
8
|
+
|
|
9
|
+
Waterfalls are the #1 performance killer. Each sequential await adds full network latency.
|
|
10
|
+
|
|
11
|
+
### 1.1 Defer Await Until Needed
|
|
12
|
+
|
|
13
|
+
Move `await` into branches where actually used.
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
// Incorrect: blocks both branches
|
|
17
|
+
async function handleRequest(userId: string, skipProcessing: boolean) {
|
|
18
|
+
const userData = await fetchUserData(userId)
|
|
19
|
+
if (skipProcessing) return { skipped: true }
|
|
20
|
+
return processUserData(userData)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Correct: only blocks when needed
|
|
24
|
+
async function handleRequest(userId: string, skipProcessing: boolean) {
|
|
25
|
+
if (skipProcessing) return { skipped: true }
|
|
26
|
+
const userData = await fetchUserData(userId)
|
|
27
|
+
return processUserData(userData)
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 1.2 Promise.all() for Independent Operations
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
// Incorrect: 3 round trips
|
|
35
|
+
const user = await fetchUser()
|
|
36
|
+
const posts = await fetchPosts()
|
|
37
|
+
const comments = await fetchComments()
|
|
38
|
+
|
|
39
|
+
// Correct: 1 round trip
|
|
40
|
+
const [user, posts, comments] = await Promise.all([
|
|
41
|
+
fetchUser(),
|
|
42
|
+
fetchPosts(),
|
|
43
|
+
fetchComments()
|
|
44
|
+
])
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 1.3 Dependency-Based Parallelization
|
|
48
|
+
|
|
49
|
+
For operations with partial dependencies, use `better-all`:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { all } from 'better-all'
|
|
53
|
+
|
|
54
|
+
const { user, config, profile } = await all({
|
|
55
|
+
async user() { return fetchUser() },
|
|
56
|
+
async config() { return fetchConfig() },
|
|
57
|
+
async profile() {
|
|
58
|
+
return fetchProfile((await this.$.user).id)
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 1.4 Strategic Suspense Boundaries
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
// Correct: wrapper shows immediately, data streams in
|
|
67
|
+
function Page() {
|
|
68
|
+
return (
|
|
69
|
+
<div>
|
|
70
|
+
<Header />
|
|
71
|
+
<Suspense fallback={<Skeleton />}>
|
|
72
|
+
<DataDisplay />
|
|
73
|
+
</Suspense>
|
|
74
|
+
<Footer />
|
|
75
|
+
</div>
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function DataDisplay() {
|
|
80
|
+
const data = await fetchData()
|
|
81
|
+
return <div>{data.content}</div>
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**When NOT to use:**
|
|
86
|
+
- Critical data needed for layout decisions
|
|
87
|
+
- SEO-critical content above the fold
|
|
88
|
+
- Small, fast queries where suspense overhead isn't worth it
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 2. Bundle Size Optimization (CRITICAL)
|
|
93
|
+
|
|
94
|
+
Keep bundle <10MB for Zalo Mini App approval.
|
|
95
|
+
|
|
96
|
+
### 2.1 Avoid Barrel File Imports
|
|
97
|
+
|
|
98
|
+
Barrel files can load 1000+ modules, taking 200-800ms extra.
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
// Incorrect: imports entire library (1,583 modules)
|
|
102
|
+
import { Check, X, Menu } from 'lucide-react'
|
|
103
|
+
|
|
104
|
+
// Correct: imports only what you need (~3 modules)
|
|
105
|
+
import Check from 'lucide-react/dist/esm/icons/check'
|
|
106
|
+
import X from 'lucide-react/dist/esm/icons/x'
|
|
107
|
+
import Menu from 'lucide-react/dist/esm/icons/menu'
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Commonly affected libraries:** `lucide-react`, `@mui/material`, `react-icons`, `lodash`, `date-fns`
|
|
111
|
+
|
|
112
|
+
### 2.2 Dynamic Imports for Heavy Components
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { lazy, Suspense } from 'react'
|
|
116
|
+
|
|
117
|
+
const HeavyEditor = lazy(() => import('./HeavyEditor'))
|
|
118
|
+
|
|
119
|
+
function CodePanel({ code }) {
|
|
120
|
+
return (
|
|
121
|
+
<Suspense fallback={<Skeleton />}>
|
|
122
|
+
<HeavyEditor value={code} />
|
|
123
|
+
</Suspense>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### 2.3 Conditional Module Loading
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
function AnimationPlayer({ enabled }) {
|
|
132
|
+
const [frames, setFrames] = useState(null)
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
if (enabled && !frames) {
|
|
136
|
+
import('./animation-frames.js')
|
|
137
|
+
.then(mod => setFrames(mod.frames))
|
|
138
|
+
.catch(() => {})
|
|
139
|
+
}
|
|
140
|
+
}, [enabled, frames])
|
|
141
|
+
|
|
142
|
+
if (!frames) return <Skeleton />
|
|
143
|
+
return <Canvas frames={frames} />
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 2.4 Preload on User Intent
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
function EditorButton({ onClick }) {
|
|
151
|
+
const preload = () => void import('./HeavyEditor')
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<button
|
|
155
|
+
onMouseEnter={preload}
|
|
156
|
+
onFocus={preload}
|
|
157
|
+
onClick={onClick}
|
|
158
|
+
>
|
|
159
|
+
Open Editor
|
|
160
|
+
</button>
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### 2.5 Defer Non-Critical Libraries
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
// Analytics, logging load after hydration
|
|
169
|
+
import { lazy, Suspense } from 'react'
|
|
170
|
+
|
|
171
|
+
const Analytics = lazy(() => import('@/lib/analytics'))
|
|
172
|
+
|
|
173
|
+
function RootLayout({ children }) {
|
|
174
|
+
return (
|
|
175
|
+
<>
|
|
176
|
+
{children}
|
|
177
|
+
<Suspense fallback={null}>
|
|
178
|
+
<Analytics />
|
|
179
|
+
</Suspense>
|
|
180
|
+
</>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 3. Client-Side Data Fetching (MEDIUM-HIGH)
|
|
188
|
+
|
|
189
|
+
### 3.1 Use SWR for Automatic Deduplication
|
|
190
|
+
|
|
191
|
+
```tsx
|
|
192
|
+
import useSWR from 'swr'
|
|
193
|
+
|
|
194
|
+
// Multiple instances share one request
|
|
195
|
+
function UserList() {
|
|
196
|
+
const { data: users } = useSWR('/api/users', fetcher)
|
|
197
|
+
return <div>{users?.map(renderUser)}</div>
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### 3.2 Passive Event Listeners for Scroll Performance
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
const handleScroll = (e) => console.log(e.deltaY)
|
|
206
|
+
|
|
207
|
+
// Correct: passive allows immediate scrolling
|
|
208
|
+
document.addEventListener('wheel', handleScroll, { passive: true })
|
|
209
|
+
|
|
210
|
+
return () => document.removeEventListener('wheel', handleScroll)
|
|
211
|
+
}, [])
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### 3.3 Deduplicate Global Event Listeners
|
|
215
|
+
|
|
216
|
+
Use `useSWRSubscription` to share listeners across component instances:
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
import useSWRSubscription from 'swr/subscription'
|
|
220
|
+
|
|
221
|
+
const keyCallbacks = new Map()
|
|
222
|
+
|
|
223
|
+
function useKeyboardShortcut(key, callback) {
|
|
224
|
+
useEffect(() => {
|
|
225
|
+
if (!keyCallbacks.has(key)) keyCallbacks.set(key, new Set())
|
|
226
|
+
keyCallbacks.get(key).add(callback)
|
|
227
|
+
return () => {
|
|
228
|
+
keyCallbacks.get(key)?.delete(callback)
|
|
229
|
+
if (keyCallbacks.get(key)?.size === 0) keyCallbacks.delete(key)
|
|
230
|
+
}
|
|
231
|
+
}, [key, callback])
|
|
232
|
+
|
|
233
|
+
useSWRSubscription('global-keydown', () => {
|
|
234
|
+
const handler = (e) => {
|
|
235
|
+
if (e.metaKey && keyCallbacks.has(e.key)) {
|
|
236
|
+
keyCallbacks.get(e.key).forEach(cb => cb())
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
window.addEventListener('keydown', handler)
|
|
240
|
+
return () => window.removeEventListener('keydown', handler)
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### 3.4 Version localStorage Data
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
const VERSION = 'v2'
|
|
249
|
+
|
|
250
|
+
function saveConfig(config) {
|
|
251
|
+
try {
|
|
252
|
+
localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config))
|
|
253
|
+
} catch {}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function loadConfig() {
|
|
257
|
+
try {
|
|
258
|
+
const data = localStorage.getItem(`userConfig:${VERSION}`)
|
|
259
|
+
return data ? JSON.parse(data) : null
|
|
260
|
+
} catch {
|
|
261
|
+
return null
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## 4. Re-render Optimization (MEDIUM)
|
|
269
|
+
|
|
270
|
+
### 4.1 Defer State Reads to Usage Point
|
|
271
|
+
|
|
272
|
+
```tsx
|
|
273
|
+
// Incorrect: subscribes to all searchParams changes
|
|
274
|
+
function ShareButton({ chatId }) {
|
|
275
|
+
const searchParams = useSearchParams()
|
|
276
|
+
const handleShare = () => shareChat(chatId, { ref: searchParams.get('ref') })
|
|
277
|
+
return <button onClick={handleShare}>Share</button>
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Correct: reads on demand, no subscription
|
|
281
|
+
function ShareButton({ chatId }) {
|
|
282
|
+
const handleShare = () => {
|
|
283
|
+
const params = new URLSearchParams(window.location.search)
|
|
284
|
+
shareChat(chatId, { ref: params.get('ref') })
|
|
285
|
+
}
|
|
286
|
+
return <button onClick={handleShare}>Share</button>
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
### 4.2 Use Functional setState Updates
|
|
291
|
+
|
|
292
|
+
```tsx
|
|
293
|
+
// Incorrect: requires state as dependency, recreated on every change
|
|
294
|
+
const addItems = useCallback((newItems) => {
|
|
295
|
+
setItems([...items, ...newItems])
|
|
296
|
+
}, [items])
|
|
297
|
+
|
|
298
|
+
// Correct: stable callback, no stale closures
|
|
299
|
+
const addItems = useCallback((newItems) => {
|
|
300
|
+
setItems(curr => [...curr, ...newItems])
|
|
301
|
+
}, [])
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Benefits:**
|
|
305
|
+
- Stable callback references
|
|
306
|
+
- No stale closures
|
|
307
|
+
- Fewer dependencies
|
|
308
|
+
- Prevents common React bugs
|
|
309
|
+
|
|
310
|
+
### 4.3 Use Lazy State Initialization
|
|
311
|
+
|
|
312
|
+
```tsx
|
|
313
|
+
// Incorrect: buildSearchIndex runs on EVERY render
|
|
314
|
+
const [searchIndex] = useState(buildSearchIndex(items))
|
|
315
|
+
|
|
316
|
+
// Correct: runs only on initial render
|
|
317
|
+
const [searchIndex] = useState(() => buildSearchIndex(items))
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### 4.4 Narrow Effect Dependencies
|
|
321
|
+
|
|
322
|
+
```tsx
|
|
323
|
+
// Incorrect: re-runs on any user field change
|
|
324
|
+
useEffect(() => {
|
|
325
|
+
console.log(user.id)
|
|
326
|
+
}, [user])
|
|
327
|
+
|
|
328
|
+
// Correct: re-runs only when id changes
|
|
329
|
+
useEffect(() => {
|
|
330
|
+
console.log(user.id)
|
|
331
|
+
}, [user.id])
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
### 4.5 Subscribe to Derived State
|
|
335
|
+
|
|
336
|
+
```tsx
|
|
337
|
+
// Incorrect: re-renders on every pixel change
|
|
338
|
+
function Sidebar() {
|
|
339
|
+
const width = useWindowWidth()
|
|
340
|
+
const isMobile = width < 768
|
|
341
|
+
return <nav className={isMobile ? 'mobile' : 'desktop'} />
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Correct: re-renders only when boolean changes
|
|
345
|
+
function Sidebar() {
|
|
346
|
+
const isMobile = useMediaQuery('(max-width: 767px)')
|
|
347
|
+
return <nav className={isMobile ? 'mobile' : 'desktop'} />
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### 4.6 Use Transitions for Non-Urgent Updates
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
import { startTransition } from 'react'
|
|
355
|
+
|
|
356
|
+
function ScrollTracker() {
|
|
357
|
+
const [scrollY, setScrollY] = useState(0)
|
|
358
|
+
|
|
359
|
+
useEffect(() => {
|
|
360
|
+
const handler = () => {
|
|
361
|
+
startTransition(() => setScrollY(window.scrollY))
|
|
362
|
+
}
|
|
363
|
+
window.addEventListener('scroll', handler, { passive: true })
|
|
364
|
+
return () => window.removeEventListener('scroll', handler)
|
|
365
|
+
}, [])
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### 4.7 Extract to Memoized Components
|
|
370
|
+
|
|
371
|
+
```tsx
|
|
372
|
+
// Incorrect: computes avatar even when loading
|
|
373
|
+
function Profile({ user, loading }) {
|
|
374
|
+
const avatar = useMemo(() => {
|
|
375
|
+
const id = computeAvatarId(user)
|
|
376
|
+
return <Avatar id={id} />
|
|
377
|
+
}, [user])
|
|
378
|
+
|
|
379
|
+
if (loading) return <Skeleton />
|
|
380
|
+
return <div>{avatar}</div>
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Correct: skips computation when loading
|
|
384
|
+
const UserAvatar = memo(function UserAvatar({ user }) {
|
|
385
|
+
const id = useMemo(() => computeAvatarId(user), [user])
|
|
386
|
+
return <Avatar id={id} />
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
function Profile({ user, loading }) {
|
|
390
|
+
if (loading) return <Skeleton />
|
|
391
|
+
return <div><UserAvatar user={user} /></div>
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## 5. Rendering Performance (MEDIUM)
|
|
398
|
+
|
|
399
|
+
### 5.1 CSS content-visibility for Long Lists
|
|
400
|
+
|
|
401
|
+
```css
|
|
402
|
+
.message-item {
|
|
403
|
+
content-visibility: auto;
|
|
404
|
+
contain-intrinsic-size: 0 80px;
|
|
405
|
+
}
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
For 1000 messages, browser skips layout/paint for ~990 off-screen items (10× faster initial render).
|
|
409
|
+
|
|
410
|
+
### 5.2 Animate SVG Wrapper
|
|
411
|
+
|
|
412
|
+
Many browsers don't have hardware acceleration for CSS animations on SVG elements.
|
|
413
|
+
|
|
414
|
+
```tsx
|
|
415
|
+
// Incorrect: no hardware acceleration
|
|
416
|
+
<svg className="animate-spin">
|
|
417
|
+
<circle cx="12" cy="12" r="10" />
|
|
418
|
+
</svg>
|
|
419
|
+
|
|
420
|
+
// Correct: hardware accelerated
|
|
421
|
+
<div className="animate-spin">
|
|
422
|
+
<svg>
|
|
423
|
+
<circle cx="12" cy="12" r="10" />
|
|
424
|
+
</svg>
|
|
425
|
+
</div>
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### 5.3 Hoist Static JSX Elements
|
|
429
|
+
|
|
430
|
+
```tsx
|
|
431
|
+
// Correct: reuses same element across renders
|
|
432
|
+
const loadingSkeleton = (
|
|
433
|
+
<div className="animate-pulse h-20 bg-gray-200" />
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
function Container({ loading }) {
|
|
437
|
+
return <div>{loading && loadingSkeleton}</div>
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
### 5.4 Use Explicit Conditional Rendering
|
|
442
|
+
|
|
443
|
+
```tsx
|
|
444
|
+
// Incorrect: renders "0" when count is 0
|
|
445
|
+
{count && <span className="badge">{count}</span>}
|
|
446
|
+
|
|
447
|
+
// Correct: renders nothing when count is 0
|
|
448
|
+
{count > 0 ? <span className="badge">{count}</span> : null}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### 5.5 Prevent Hydration Mismatch Without Flickering
|
|
452
|
+
|
|
453
|
+
```tsx
|
|
454
|
+
function ThemeWrapper({ children }) {
|
|
455
|
+
return (
|
|
456
|
+
<>
|
|
457
|
+
<div id="theme-wrapper">
|
|
458
|
+
{children}
|
|
459
|
+
</div>
|
|
460
|
+
<script
|
|
461
|
+
dangerouslySetInnerHTML={{
|
|
462
|
+
__html: `
|
|
463
|
+
(function() {
|
|
464
|
+
try {
|
|
465
|
+
var theme = localStorage.getItem('theme') || 'light';
|
|
466
|
+
var el = document.getElementById('theme-wrapper');
|
|
467
|
+
if (el) el.className = theme;
|
|
468
|
+
} catch (e) {}
|
|
469
|
+
})();
|
|
470
|
+
`,
|
|
471
|
+
}}
|
|
472
|
+
/>
|
|
473
|
+
</>
|
|
474
|
+
)
|
|
475
|
+
}
|
|
476
|
+
```
|
|
477
|
+
|
|
478
|
+
### 5.6 Use Activity Component for Show/Hide
|
|
479
|
+
|
|
480
|
+
```tsx
|
|
481
|
+
import { Activity } from 'react'
|
|
482
|
+
|
|
483
|
+
function Dropdown({ isOpen }) {
|
|
484
|
+
return (
|
|
485
|
+
<Activity mode={isOpen ? 'visible' : 'hidden'}>
|
|
486
|
+
<ExpensiveMenu />
|
|
487
|
+
</Activity>
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Preserves state/DOM for expensive components that frequently toggle visibility.
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
## 6. JavaScript Performance (LOW-MEDIUM)
|
|
497
|
+
|
|
498
|
+
### 6.1 Build Index Maps for Repeated Lookups
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
// Incorrect: O(n) per lookup - 1M ops for 1000×1000
|
|
502
|
+
orders.map(order => ({
|
|
503
|
+
...order,
|
|
504
|
+
user: users.find(u => u.id === order.userId)
|
|
505
|
+
}))
|
|
506
|
+
|
|
507
|
+
// Correct: O(1) per lookup - 2K ops
|
|
508
|
+
const userById = new Map(users.map(u => [u.id, u]))
|
|
509
|
+
orders.map(order => ({
|
|
510
|
+
...order,
|
|
511
|
+
user: userById.get(order.userId)
|
|
512
|
+
}))
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### 6.2 Use Set/Map for O(1) Lookups
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
// Incorrect: O(n) per check
|
|
519
|
+
items.filter(item => allowedIds.includes(item.id))
|
|
520
|
+
|
|
521
|
+
// Correct: O(1) per check
|
|
522
|
+
const allowedSet = new Set(allowedIds)
|
|
523
|
+
items.filter(item => allowedSet.has(item.id))
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
### 6.3 Combine Multiple Array Iterations
|
|
527
|
+
|
|
528
|
+
```typescript
|
|
529
|
+
// Incorrect: 3 iterations
|
|
530
|
+
const admins = users.filter(u => u.isAdmin)
|
|
531
|
+
const testers = users.filter(u => u.isTester)
|
|
532
|
+
const inactive = users.filter(u => !u.isActive)
|
|
533
|
+
|
|
534
|
+
// Correct: 1 iteration
|
|
535
|
+
const admins = [], testers = [], inactive = []
|
|
536
|
+
for (const user of users) {
|
|
537
|
+
if (user.isAdmin) admins.push(user)
|
|
538
|
+
if (user.isTester) testers.push(user)
|
|
539
|
+
if (!user.isActive) inactive.push(user)
|
|
540
|
+
}
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### 6.4 Cache Storage API Calls
|
|
544
|
+
|
|
545
|
+
```typescript
|
|
546
|
+
const storageCache = new Map()
|
|
547
|
+
|
|
548
|
+
function getLocalStorage(key) {
|
|
549
|
+
if (!storageCache.has(key)) {
|
|
550
|
+
storageCache.set(key, localStorage.getItem(key))
|
|
551
|
+
}
|
|
552
|
+
return storageCache.get(key)
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function setLocalStorage(key, value) {
|
|
556
|
+
localStorage.setItem(key, value)
|
|
557
|
+
storageCache.set(key, value)
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Invalidate on external changes
|
|
561
|
+
window.addEventListener('storage', (e) => {
|
|
562
|
+
if (e.key) storageCache.delete(e.key)
|
|
563
|
+
})
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
### 6.5 Cache Repeated Function Calls
|
|
567
|
+
|
|
568
|
+
```typescript
|
|
569
|
+
const slugifyCache = new Map()
|
|
570
|
+
|
|
571
|
+
function cachedSlugify(text) {
|
|
572
|
+
if (slugifyCache.has(text)) return slugifyCache.get(text)
|
|
573
|
+
const result = slugify(text)
|
|
574
|
+
slugifyCache.set(text, result)
|
|
575
|
+
return result
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
### 6.6 Use toSorted() for Immutability
|
|
580
|
+
|
|
581
|
+
```typescript
|
|
582
|
+
// Incorrect: mutates original array
|
|
583
|
+
const sorted = users.sort((a, b) => a.name.localeCompare(b.name))
|
|
584
|
+
|
|
585
|
+
// Correct: creates new array, original unchanged
|
|
586
|
+
const sorted = users.toSorted((a, b) => a.name.localeCompare(b.name))
|
|
587
|
+
|
|
588
|
+
// Fallback for older browsers
|
|
589
|
+
const sorted = [...users].sort((a, b) => a.name.localeCompare(b.name))
|
|
590
|
+
```
|
|
591
|
+
|
|
592
|
+
**Other immutable methods:** `.toReversed()`, `.toSpliced()`, `.with()`
|
|
593
|
+
|
|
594
|
+
### 6.7 Early Return from Functions
|
|
595
|
+
|
|
596
|
+
```typescript
|
|
597
|
+
// Incorrect: processes all items after error found
|
|
598
|
+
function validateUsers(users) {
|
|
599
|
+
let hasError = false
|
|
600
|
+
for (const user of users) {
|
|
601
|
+
if (!user.email) hasError = true
|
|
602
|
+
}
|
|
603
|
+
return hasError ? { valid: false } : { valid: true }
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Correct: returns on first error
|
|
607
|
+
function validateUsers(users) {
|
|
608
|
+
for (const user of users) {
|
|
609
|
+
if (!user.email) return { valid: false, error: 'Email required' }
|
|
610
|
+
}
|
|
611
|
+
return { valid: true }
|
|
612
|
+
}
|
|
613
|
+
```
|
|
614
|
+
|
|
615
|
+
### 6.8 Early Length Check for Array Comparisons
|
|
616
|
+
|
|
617
|
+
```typescript
|
|
618
|
+
// Incorrect: always sorts even when lengths differ
|
|
619
|
+
function hasChanges(current, original) {
|
|
620
|
+
return current.sort().join() !== original.sort().join()
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Correct: O(1) length check first
|
|
624
|
+
function hasChanges(current, original) {
|
|
625
|
+
if (current.length !== original.length) return true
|
|
626
|
+
const currentSorted = current.toSorted()
|
|
627
|
+
const originalSorted = original.toSorted()
|
|
628
|
+
for (let i = 0; i < currentSorted.length; i++) {
|
|
629
|
+
if (currentSorted[i] !== originalSorted[i]) return true
|
|
630
|
+
}
|
|
631
|
+
return false
|
|
632
|
+
}
|
|
633
|
+
```
|
|
634
|
+
|
|
635
|
+
### 6.9 Use Loop for Min/Max Instead of Sort
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
// Incorrect: O(n log n)
|
|
639
|
+
function getLatestProject(projects) {
|
|
640
|
+
const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt)
|
|
641
|
+
return sorted[0]
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// Correct: O(n)
|
|
645
|
+
function getLatestProject(projects) {
|
|
646
|
+
if (projects.length === 0) return null
|
|
647
|
+
let latest = projects[0]
|
|
648
|
+
for (let i = 1; i < projects.length; i++) {
|
|
649
|
+
if (projects[i].updatedAt > latest.updatedAt) {
|
|
650
|
+
latest = projects[i]
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return latest
|
|
654
|
+
}
|
|
655
|
+
```
|
|
656
|
+
|
|
657
|
+
### 6.10 Cache Property Access in Loops
|
|
658
|
+
|
|
659
|
+
```typescript
|
|
660
|
+
// Incorrect: 3 lookups × N iterations
|
|
661
|
+
for (let i = 0; i < arr.length; i++) {
|
|
662
|
+
process(obj.config.settings.value)
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Correct: 1 lookup total
|
|
666
|
+
const value = obj.config.settings.value
|
|
667
|
+
const len = arr.length
|
|
668
|
+
for (let i = 0; i < len; i++) {
|
|
669
|
+
process(value)
|
|
670
|
+
}
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
### 6.11 Hoist RegExp Creation
|
|
674
|
+
|
|
675
|
+
```tsx
|
|
676
|
+
// Incorrect: new RegExp every render
|
|
677
|
+
function Highlighter({ text, query }) {
|
|
678
|
+
const regex = new RegExp(`(${query})`, 'gi')
|
|
679
|
+
const parts = text.split(regex)
|
|
680
|
+
return <>{parts.map((part, i) => ...)}</>
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
// Correct: memoize
|
|
684
|
+
function Highlighter({ text, query }) {
|
|
685
|
+
const regex = useMemo(
|
|
686
|
+
() => new RegExp(`(${escapeRegex(query)})`, 'gi'),
|
|
687
|
+
[query]
|
|
688
|
+
)
|
|
689
|
+
const parts = text.split(regex)
|
|
690
|
+
return <>{parts.map((part, i) => ...)}</>
|
|
691
|
+
}
|
|
692
|
+
```
|
|
693
|
+
|
|
694
|
+
### 6.12 Batch DOM CSS Changes
|
|
695
|
+
|
|
696
|
+
```typescript
|
|
697
|
+
// Incorrect: multiple reflows
|
|
698
|
+
element.style.width = '100px'
|
|
699
|
+
element.style.height = '200px'
|
|
700
|
+
element.style.backgroundColor = 'blue'
|
|
701
|
+
|
|
702
|
+
// Correct: single reflow via class
|
|
703
|
+
element.classList.add('highlighted-box')
|
|
704
|
+
|
|
705
|
+
// Or via cssText
|
|
706
|
+
element.style.cssText = `
|
|
707
|
+
width: 100px;
|
|
708
|
+
height: 200px;
|
|
709
|
+
background-color: blue;
|
|
710
|
+
`
|
|
711
|
+
```
|
|
712
|
+
|
|
713
|
+
---
|
|
714
|
+
|
|
715
|
+
## 7. Advanced Patterns (LOW)
|
|
716
|
+
|
|
717
|
+
### 7.1 Store Event Handlers in Refs
|
|
718
|
+
|
|
719
|
+
```tsx
|
|
720
|
+
import { useEffectEvent } from 'react'
|
|
721
|
+
|
|
722
|
+
function useWindowEvent(event, handler) {
|
|
723
|
+
const onEvent = useEffectEvent(handler)
|
|
724
|
+
|
|
725
|
+
useEffect(() => {
|
|
726
|
+
window.addEventListener(event, onEvent)
|
|
727
|
+
return () => window.removeEventListener(event, onEvent)
|
|
728
|
+
}, [event])
|
|
729
|
+
}
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
### 7.2 useLatest for Stable Callback Refs
|
|
733
|
+
|
|
734
|
+
```typescript
|
|
735
|
+
function useLatest(value) {
|
|
736
|
+
const ref = useRef(value)
|
|
737
|
+
useEffect(() => {
|
|
738
|
+
ref.current = value
|
|
739
|
+
}, [value])
|
|
740
|
+
return ref
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
// Usage: stable effect, fresh callback
|
|
744
|
+
function SearchInput({ onSearch }) {
|
|
745
|
+
const [query, setQuery] = useState('')
|
|
746
|
+
const onSearchRef = useLatest(onSearch)
|
|
747
|
+
|
|
748
|
+
useEffect(() => {
|
|
749
|
+
const timeout = setTimeout(() => onSearchRef.current(query), 300)
|
|
750
|
+
return () => clearTimeout(timeout)
|
|
751
|
+
}, [query])
|
|
752
|
+
}
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
---
|
|
756
|
+
|
|
757
|
+
## Anti-patterns Checklist
|
|
758
|
+
|
|
759
|
+
### Critical Performance
|
|
760
|
+
- [ ] Sequential awaits for independent operations
|
|
761
|
+
- [ ] Barrel file imports (`import { X } from 'library'`)
|
|
762
|
+
- [ ] Large arrays `.map()` without virtualization
|
|
763
|
+
- [ ] `transition: all` instead of specific properties
|
|
764
|
+
|
|
765
|
+
### React State
|
|
766
|
+
- [ ] Mutating arrays with `.sort()` (use `.toSorted()`)
|
|
767
|
+
- [ ] Missing functional setState for state-dependent updates
|
|
768
|
+
- [ ] Non-lazy expensive state initialization
|
|
769
|
+
- [ ] Object dependencies in useEffect (use primitives)
|
|
770
|
+
|
|
771
|
+
### Rendering
|
|
772
|
+
- [ ] Animating SVG elements directly (wrap in div)
|
|
773
|
+
- [ ] Using `&&` with numbers (`count && <Badge />`)
|
|
774
|
+
- [ ] Creating RegExp in render without memoization
|
|
775
|
+
- [ ] Layout reads in render (`getBoundingClientRect`)
|
|
776
|
+
|
|
777
|
+
### Events
|
|
778
|
+
- [ ] Missing `{ passive: true }` for scroll/touch listeners
|
|
779
|
+
- [ ] Multiple listeners for same global event
|
|
780
|
+
- [ ] Non-versioned localStorage keys
|
|
781
|
+
|
|
782
|
+
---
|
|
783
|
+
|
|
784
|
+
## References
|
|
785
|
+
|
|
786
|
+
- React: https://react.dev
|
|
787
|
+
- SWR: https://swr.vercel.app
|
|
788
|
+
- better-all: https://github.com/shuding/better-all
|
|
789
|
+
- LRU Cache: https://github.com/isaacs/node-lru-cache
|
|
790
|
+
- Vercel Blog: https://vercel.com/blog/how-we-made-the-vercel-dashboard-twice-as-fast
|