@mison/ag-kit-cn 2.0.1

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.
Files changed (237) hide show
  1. package/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
  2. package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
  3. package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
  4. package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
  5. package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
  6. package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
  7. package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
  8. package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
  9. package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
  10. package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
  11. package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
  12. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
  13. package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
  14. package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
  15. package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
  16. package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
  17. package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
  18. package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
  19. package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
  20. package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
  21. package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
  22. package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
  23. package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
  24. package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
  25. package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
  26. package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
  27. package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
  28. package/.agent/ARCHITECTURE.md +285 -0
  29. package/.agent/agents/backend-specialist.md +268 -0
  30. package/.agent/agents/code-archaeologist.md +106 -0
  31. package/.agent/agents/database-architect.md +225 -0
  32. package/.agent/agents/debugger.md +225 -0
  33. package/.agent/agents/devops-engineer.md +242 -0
  34. package/.agent/agents/documentation-writer.md +104 -0
  35. package/.agent/agents/explorer-agent.md +73 -0
  36. package/.agent/agents/frontend-specialist.md +618 -0
  37. package/.agent/agents/game-developer.md +162 -0
  38. package/.agent/agents/mobile-developer.md +382 -0
  39. package/.agent/agents/orchestrator.md +438 -0
  40. package/.agent/agents/penetration-tester.md +188 -0
  41. package/.agent/agents/performance-optimizer.md +187 -0
  42. package/.agent/agents/product-manager.md +112 -0
  43. package/.agent/agents/product-owner.md +95 -0
  44. package/.agent/agents/project-planner.md +405 -0
  45. package/.agent/agents/qa-automation-engineer.md +103 -0
  46. package/.agent/agents/security-auditor.md +170 -0
  47. package/.agent/agents/seo-specialist.md +111 -0
  48. package/.agent/agents/test-engineer.md +158 -0
  49. package/.agent/mcp_config.json +12 -0
  50. package/.agent/rules/GEMINI.md +273 -0
  51. package/.agent/scripts/auto_preview.py +148 -0
  52. package/.agent/scripts/checklist.py +217 -0
  53. package/.agent/scripts/session_manager.py +120 -0
  54. package/.agent/scripts/verify_all.py +327 -0
  55. package/.agent/skills/api-patterns/SKILL.md +84 -0
  56. package/.agent/skills/api-patterns/api-style.md +42 -0
  57. package/.agent/skills/api-patterns/auth.md +24 -0
  58. package/.agent/skills/api-patterns/documentation.md +26 -0
  59. package/.agent/skills/api-patterns/graphql.md +41 -0
  60. package/.agent/skills/api-patterns/rate-limiting.md +31 -0
  61. package/.agent/skills/api-patterns/response.md +37 -0
  62. package/.agent/skills/api-patterns/rest.md +40 -0
  63. package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
  64. package/.agent/skills/api-patterns/security-testing.md +122 -0
  65. package/.agent/skills/api-patterns/trpc.md +41 -0
  66. package/.agent/skills/api-patterns/versioning.md +22 -0
  67. package/.agent/skills/app-builder/SKILL.md +75 -0
  68. package/.agent/skills/app-builder/agent-coordination.md +74 -0
  69. package/.agent/skills/app-builder/feature-building.md +53 -0
  70. package/.agent/skills/app-builder/project-detection.md +34 -0
  71. package/.agent/skills/app-builder/scaffolding.md +118 -0
  72. package/.agent/skills/app-builder/tech-stack.md +40 -0
  73. package/.agent/skills/app-builder/templates/SKILL.md +39 -0
  74. package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
  75. package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
  76. package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
  77. package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
  78. package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
  79. package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
  80. package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
  81. package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
  82. package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
  83. package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
  84. package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
  85. package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
  86. package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
  87. package/.agent/skills/architecture/SKILL.md +57 -0
  88. package/.agent/skills/architecture/context-discovery.md +43 -0
  89. package/.agent/skills/architecture/examples.md +94 -0
  90. package/.agent/skills/architecture/pattern-selection.md +68 -0
  91. package/.agent/skills/architecture/patterns-reference.md +50 -0
  92. package/.agent/skills/architecture/trade-off-analysis.md +77 -0
  93. package/.agent/skills/bash-linux/SKILL.md +201 -0
  94. package/.agent/skills/behavioral-modes/SKILL.md +264 -0
  95. package/.agent/skills/brainstorming/SKILL.md +164 -0
  96. package/.agent/skills/brainstorming/dynamic-questioning.md +359 -0
  97. package/.agent/skills/clean-code/SKILL.md +200 -0
  98. package/.agent/skills/code-review-checklist/SKILL.md +125 -0
  99. package/.agent/skills/database-design/SKILL.md +54 -0
  100. package/.agent/skills/database-design/database-selection.md +43 -0
  101. package/.agent/skills/database-design/indexing.md +39 -0
  102. package/.agent/skills/database-design/migrations.md +50 -0
  103. package/.agent/skills/database-design/optimization.md +36 -0
  104. package/.agent/skills/database-design/orm-selection.md +30 -0
  105. package/.agent/skills/database-design/schema-design.md +56 -0
  106. package/.agent/skills/database-design/scripts/schema_validator.py +172 -0
  107. package/.agent/skills/deployment-procedures/SKILL.md +241 -0
  108. package/.agent/skills/doc.md +177 -0
  109. package/.agent/skills/documentation-templates/SKILL.md +194 -0
  110. package/.agent/skills/frontend-design/SKILL.md +418 -0
  111. package/.agent/skills/frontend-design/animation-guide.md +331 -0
  112. package/.agent/skills/frontend-design/color-system.md +307 -0
  113. package/.agent/skills/frontend-design/decision-trees.md +418 -0
  114. package/.agent/skills/frontend-design/motion-graphics.md +306 -0
  115. package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
  116. package/.agent/skills/frontend-design/scripts/ux_audit.py +727 -0
  117. package/.agent/skills/frontend-design/typography-system.md +345 -0
  118. package/.agent/skills/frontend-design/ux-psychology.md +1118 -0
  119. package/.agent/skills/frontend-design/visual-effects.md +383 -0
  120. package/.agent/skills/game-development/2d-games/SKILL.md +119 -0
  121. package/.agent/skills/game-development/3d-games/SKILL.md +135 -0
  122. package/.agent/skills/game-development/SKILL.md +167 -0
  123. package/.agent/skills/game-development/game-art/SKILL.md +185 -0
  124. package/.agent/skills/game-development/game-audio/SKILL.md +190 -0
  125. package/.agent/skills/game-development/game-design/SKILL.md +129 -0
  126. package/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
  127. package/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
  128. package/.agent/skills/game-development/pc-games/SKILL.md +144 -0
  129. package/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
  130. package/.agent/skills/game-development/web-games/SKILL.md +150 -0
  131. package/.agent/skills/geo-fundamentals/SKILL.md +155 -0
  132. package/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
  133. package/.agent/skills/i18n-localization/SKILL.md +154 -0
  134. package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
  135. package/.agent/skills/intelligent-routing/SKILL.md +335 -0
  136. package/.agent/skills/lint-and-validate/SKILL.md +44 -0
  137. package/.agent/skills/lint-and-validate/scripts/lint_runner.py +184 -0
  138. package/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
  139. package/.agent/skills/mcp-builder/SKILL.md +176 -0
  140. package/.agent/skills/mobile-design/SKILL.md +394 -0
  141. package/.agent/skills/mobile-design/decision-trees.md +516 -0
  142. package/.agent/skills/mobile-design/mobile-backend.md +491 -0
  143. package/.agent/skills/mobile-design/mobile-color-system.md +420 -0
  144. package/.agent/skills/mobile-design/mobile-debugging.md +122 -0
  145. package/.agent/skills/mobile-design/mobile-design-thinking.md +355 -0
  146. package/.agent/skills/mobile-design/mobile-navigation.md +458 -0
  147. package/.agent/skills/mobile-design/mobile-performance.md +767 -0
  148. package/.agent/skills/mobile-design/mobile-testing.md +356 -0
  149. package/.agent/skills/mobile-design/mobile-typography.md +432 -0
  150. package/.agent/skills/mobile-design/platform-android.md +666 -0
  151. package/.agent/skills/mobile-design/platform-ios.md +561 -0
  152. package/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
  153. package/.agent/skills/mobile-design/touch-psychology.md +537 -0
  154. package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +311 -0
  155. package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +241 -0
  156. package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +489 -0
  157. package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +263 -0
  158. package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
  159. package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +431 -0
  160. package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +683 -0
  161. package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +149 -0
  162. package/.agent/skills/nextjs-react-expert/SKILL.md +286 -0
  163. package/.agent/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
  164. package/.agent/skills/nextjs-react-expert/scripts/react_performance_checker.py +252 -0
  165. package/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
  166. package/.agent/skills/parallel-agents/SKILL.md +194 -0
  167. package/.agent/skills/performance-profiling/SKILL.md +149 -0
  168. package/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
  169. package/.agent/skills/plan-writing/SKILL.md +152 -0
  170. package/.agent/skills/powershell-windows/SKILL.md +166 -0
  171. package/.agent/skills/python-patterns/SKILL.md +441 -0
  172. package/.agent/skills/red-team-tactics/SKILL.md +203 -0
  173. package/.agent/skills/rust-pro/SKILL.md +190 -0
  174. package/.agent/skills/seo-fundamentals/SKILL.md +135 -0
  175. package/.agent/skills/seo-fundamentals/scripts/seo_checker.py +215 -0
  176. package/.agent/skills/server-management/SKILL.md +161 -0
  177. package/.agent/skills/systematic-debugging/SKILL.md +114 -0
  178. package/.agent/skills/tailwind-patterns/SKILL.md +269 -0
  179. package/.agent/skills/tdd-workflow/SKILL.md +149 -0
  180. package/.agent/skills/testing-patterns/SKILL.md +178 -0
  181. package/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
  182. package/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
  183. package/.agent/skills/vulnerability-scanner/checklists.md +131 -0
  184. package/.agent/skills/vulnerability-scanner/scripts/security_scan.py +459 -0
  185. package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
  186. package/.agent/skills/webapp-testing/SKILL.md +187 -0
  187. package/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
  188. package/.agent/workflows/brainstorm.md +113 -0
  189. package/.agent/workflows/create.md +59 -0
  190. package/.agent/workflows/debug.md +103 -0
  191. package/.agent/workflows/deploy.md +176 -0
  192. package/.agent/workflows/enhance.md +63 -0
  193. package/.agent/workflows/orchestrate.md +242 -0
  194. package/.agent/workflows/plan.md +89 -0
  195. package/.agent/workflows/preview.md +80 -0
  196. package/.agent/workflows/restore-localize-compat.md +525 -0
  197. package/.agent/workflows/status.md +86 -0
  198. package/.agent/workflows/test.md +144 -0
  199. package/.agent/workflows/ui-ux-pro-max.md +295 -0
  200. package/AGENT_FLOW.md +609 -0
  201. package/CHANGELOG.md +68 -0
  202. package/LICENSE +21 -0
  203. package/README.md +260 -0
  204. package/bin/adapters/base.js +63 -0
  205. package/bin/adapters/codex.js +391 -0
  206. package/bin/adapters/gemini.js +137 -0
  207. package/bin/ag-kit.js +1336 -0
  208. package/bin/core/builder.js +80 -0
  209. package/bin/core/generator.js +59 -0
  210. package/bin/core/resource-loader.js +64 -0
  211. package/bin/core/transformer.js +208 -0
  212. package/bin/interactive.js +65 -0
  213. package/bin/utils/atomic-writer.js +97 -0
  214. package/bin/utils/git-helper.js +68 -0
  215. package/bin/utils/managed-block.js +65 -0
  216. package/bin/utils/manifest.js +241 -0
  217. package/bin/utils.js +82 -0
  218. package/docs/codex-rules-template.md +36 -0
  219. package/docs/mapping-spec.md +68 -0
  220. package/docs/multi-target-adapter.md +80 -0
  221. package/docs/official/README.md +53 -0
  222. package/docs/official/antigravity/agent-modes-settings.md +64 -0
  223. package/docs/official/antigravity/rules-workflows.md +96 -0
  224. package/docs/official/antigravity/skills.md +147 -0
  225. package/docs/official/codex/agents-md.md +119 -0
  226. package/docs/official/codex/config-advanced.md +358 -0
  227. package/docs/official/codex/config-basic.md +141 -0
  228. package/docs/official/codex/config-reference.md +223 -0
  229. package/docs/official/codex/config-sample.md +216 -0
  230. package/docs/official/codex/mcp.md +107 -0
  231. package/docs/official/codex/rules.md +79 -0
  232. package/docs/official/codex/skills.md +114 -0
  233. package/docs/official/sources-index.md +32 -0
  234. package/docs/operations.md +145 -0
  235. package/docs/terminology-style-guide.md +69 -0
  236. package/package.json +51 -0
  237. package/scripts/postinstall-check.js +112 -0
@@ -0,0 +1,581 @@
1
+ # 5. 重渲染优化 (Re-render Optimization)
2
+
3
+ > **影响:** 中 (MEDIUM)
4
+ > **重点:** 减少不必要的重渲染,降低无效计算并提升 UI 响应性。
5
+
6
+ ---
7
+
8
+ ## 概览
9
+
10
+ 本节包含 **12 条规则**,聚焦重渲染优化。
11
+
12
+ ---
13
+
14
+ ## 规则 5.1:在渲染阶段计算派生状态
15
+
16
+ **影响:** 中 (MEDIUM)
17
+ **标签:** rerender, derived-state, useEffect, state
18
+
19
+ ## 在渲染阶段计算派生状态
20
+
21
+ 如果一个值可以由当前 props/state 直接计算得到,就不要再把它存进 state,或通过 effect 去更新它。应在渲染阶段直接派生,避免额外渲染和状态漂移。不要仅因为 prop 变化就在 effect 里 setState;优先使用派生值或基于 key 的重置。
22
+
23
+ **错误示例(冗余 state + effect):**
24
+
25
+ ```tsx
26
+ function Form() {
27
+ const [firstName, setFirstName] = useState('First')
28
+ const [lastName, setLastName] = useState('Last')
29
+ const [fullName, setFullName] = useState('')
30
+
31
+ useEffect(() => {
32
+ setFullName(firstName + ' ' + lastName)
33
+ }, [firstName, lastName])
34
+
35
+ return <p>{fullName}</p>
36
+ }
37
+ ```
38
+
39
+ **正确示例(渲染时直接派生):**
40
+
41
+ ```tsx
42
+ function Form() {
43
+ const [firstName, setFirstName] = useState('First')
44
+ const [lastName, setLastName] = useState('Last')
45
+ const fullName = firstName + ' ' + lastName
46
+
47
+ return <p>{fullName}</p>
48
+ }
49
+ ```
50
+
51
+ 参考: [You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)
52
+
53
+ ---
54
+
55
+ ## 规则 5.2:将状态读取延后到实际使用点
56
+
57
+ **影响:** 中 (MEDIUM)
58
+ **标签:** rerender, searchParams, localStorage, optimization
59
+
60
+ ## 将状态读取延后到实际使用点
61
+
62
+ 如果你只在回调里读取动态状态(如 searchParams、localStorage),就不要为其建立订阅。
63
+
64
+ **错误示例(会订阅所有 searchParams 变化):**
65
+
66
+ ```tsx
67
+ function ShareButton({ chatId }: { chatId: string }) {
68
+ const searchParams = useSearchParams()
69
+
70
+ const handleShare = () => {
71
+ const ref = searchParams.get('ref')
72
+ shareChat(chatId, { ref })
73
+ }
74
+
75
+ return <button onClick={handleShare}>Share</button>
76
+ }
77
+ ```
78
+
79
+ **正确示例(按需读取,不订阅):**
80
+
81
+ ```tsx
82
+ function ShareButton({ chatId }: { chatId: string }) {
83
+ const handleShare = () => {
84
+ const params = new URLSearchParams(window.location.search)
85
+ const ref = params.get('ref')
86
+ shareChat(chatId, { ref })
87
+ }
88
+
89
+ return <button onClick={handleShare}>Share</button>
90
+ }
91
+ ```
92
+
93
+ ---
94
+
95
+ ## 规则 5.3:简单原始类型表达式不要用 useMemo 包裹
96
+
97
+ **影响:** 低到中 (LOW-MEDIUM)
98
+ **标签:** rerender, useMemo, optimization
99
+
100
+ ## 简单原始类型表达式不要用 useMemo 包裹
101
+
102
+ 当表达式本身很简单(仅少量逻辑或算术运算),且返回原始类型(boolean、number、string)时,不要用 `useMemo` 包裹。调用 `useMemo` 及比较依赖项的开销,可能比表达式本身更高。
103
+
104
+ **错误示例:**
105
+
106
+ ```tsx
107
+ function Header({ user, notifications }: Props) {
108
+ const isLoading = useMemo(() => {
109
+ return user.isLoading || notifications.isLoading
110
+ }, [user.isLoading, notifications.isLoading])
111
+
112
+ if (isLoading) return <Skeleton />
113
+ // return some markup
114
+ }
115
+ ```
116
+
117
+ **正确示例:**
118
+
119
+ ```tsx
120
+ function Header({ user, notifications }: Props) {
121
+ const isLoading = user.isLoading || notifications.isLoading
122
+
123
+ if (isLoading) return <Skeleton />
124
+ // return some markup
125
+ }
126
+ ```
127
+
128
+ ---
129
+
130
+ ## 规则 5.4:将 memo 组件中的非原始默认参数提取为常量
131
+
132
+ **影响:** 中 (MEDIUM)
133
+ **标签:** rerender, memo, optimization
134
+
135
+ ## 将 memo 组件中的非原始默认参数提取为常量
136
+
137
+ 当 memo 组件的可选参数默认值是非原始类型(数组、函数、对象)时,若调用组件时省略该参数,可能导致 memo 失效。原因是每次重渲染都会创建新实例,无法通过 `memo()` 的严格相等比较。
138
+
139
+ 解决方式是把默认值提取到组件外的常量中。
140
+
141
+ **错误示例(`onClick` 每次重渲染值都不同):**
142
+
143
+ ```tsx
144
+ const UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) {
145
+ // ...
146
+ })
147
+
148
+ // Used without optional onClick
149
+ <UserAvatar />
150
+ ```
151
+
152
+ **正确示例(稳定默认值):**
153
+
154
+ ```tsx
155
+ const NOOP = () => {};
156
+
157
+ const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) {
158
+ // ...
159
+ })
160
+
161
+ // Used without optional onClick
162
+ <UserAvatar />
163
+ ```
164
+
165
+ ---
166
+
167
+ ## 规则 5.5:将重计算逻辑提取到 memo 组件
168
+
169
+ **影响:** 中 (MEDIUM)
170
+ **标签:** rerender, memo, useMemo, optimization
171
+
172
+ ## 将重计算逻辑提取到 memo 组件
173
+
174
+ 把开销较大的计算提取到 memo 组件中,便于在计算前提前返回。
175
+
176
+ **错误示例(即使 loading 也计算 avatar):**
177
+
178
+ ```tsx
179
+ function Profile({ user, loading }: Props) {
180
+ const avatar = useMemo(() => {
181
+ const id = computeAvatarId(user)
182
+ return <Avatar id={id} />
183
+ }, [user])
184
+
185
+ if (loading) return <Skeleton />
186
+ return <div>{avatar}</div>
187
+ }
188
+ ```
189
+
190
+ **正确示例(loading 时跳过计算):**
191
+
192
+ ```tsx
193
+ const UserAvatar = memo(function UserAvatar({ user }: { user: User }) {
194
+ const id = useMemo(() => computeAvatarId(user), [user])
195
+ return <Avatar id={id} />
196
+ })
197
+
198
+ function Profile({ user, loading }: Props) {
199
+ if (loading) return <Skeleton />
200
+ return (
201
+ <div>
202
+ <UserAvatar user={user} />
203
+ </div>
204
+ )
205
+ }
206
+ ```
207
+
208
+ **说明:** 若项目启用 [React Compiler](https://react.dev/learn/react-compiler),通常无需手写 `memo()` 与 `useMemo()`;编译器会自动优化重渲染。
209
+
210
+ ---
211
+
212
+ ## 规则 5.6:收窄 Effect 依赖
213
+
214
+ **影响:** 低 (LOW)
215
+ **标签:** rerender, useEffect, dependencies, optimization
216
+
217
+ ## 收窄 Effect 依赖
218
+
219
+ 优先依赖原始值,而非对象整体,以减少 effect 的重复执行。
220
+
221
+ **错误示例(user 任意字段变化都会重跑):**
222
+
223
+ ```tsx
224
+ useEffect(() => {
225
+ console.log(user.id)
226
+ }, [user])
227
+ ```
228
+
229
+ **正确示例(仅 id 变化时重跑):**
230
+
231
+ ```tsx
232
+ useEffect(() => {
233
+ console.log(user.id)
234
+ }, [user.id])
235
+ ```
236
+
237
+ **派生状态应在 effect 外计算:**
238
+
239
+ ```tsx
240
+ // 错误示例:width=767、766、765...都会运行
241
+ useEffect(() => {
242
+ if (width < 768) {
243
+ enableMobileMode()
244
+ }
245
+ }, [width])
246
+
247
+ // 正确示例:仅在布尔值切换时运行
248
+ const isMobile = width < 768
249
+ useEffect(() => {
250
+ if (isMobile) {
251
+ enableMobileMode()
252
+ }
253
+ }, [isMobile])
254
+ ```
255
+
256
+ ---
257
+
258
+ ## 规则 5.7:交互逻辑应放在事件处理器中
259
+
260
+ **影响:** 中 (MEDIUM)
261
+ **标签:** rerender, useEffect, events, side-effects, dependencies
262
+
263
+ ## 交互逻辑应放在事件处理器中
264
+
265
+ 如果副作用由明确的用户操作触发(提交、点击、拖拽),就应放在对应事件处理器里执行。不要建模成“state + effect”,这会让 effect 因无关变化重跑,甚至重复触发动作。
266
+
267
+ **错误示例(把事件建模成 state + effect):**
268
+
269
+ ```tsx
270
+ function Form() {
271
+ const [submitted, setSubmitted] = useState(false)
272
+ const theme = useContext(ThemeContext)
273
+
274
+ useEffect(() => {
275
+ if (submitted) {
276
+ post('/api/register')
277
+ showToast('Registered', theme)
278
+ }
279
+ }, [submitted, theme])
280
+
281
+ return <button onClick={() => setSubmitted(true)}>Submit</button>
282
+ }
283
+ ```
284
+
285
+ **正确示例(在 handler 中直接执行):**
286
+
287
+ ```tsx
288
+ function Form() {
289
+ const theme = useContext(ThemeContext)
290
+
291
+ function handleSubmit() {
292
+ post('/api/register')
293
+ showToast('Registered', theme)
294
+ }
295
+
296
+ return <button onClick={handleSubmit}>Submit</button>
297
+ }
298
+ ```
299
+
300
+ 参考: [Should this code move to an event handler?](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler)
301
+
302
+ ---
303
+
304
+ ## 规则 5.8:订阅派生状态而非连续值
305
+
306
+ **影响:** 中 (MEDIUM)
307
+ **标签:** rerender, derived-state, media-query, optimization
308
+
309
+ ## 订阅派生状态而非连续值
310
+
311
+ 用派生布尔状态替代连续值订阅,可以降低重渲染频率。
312
+
313
+ **错误示例(每个像素变化都会重渲染):**
314
+
315
+ ```tsx
316
+ function Sidebar() {
317
+ const width = useWindowWidth() // updates continuously
318
+ const isMobile = width < 768
319
+ return <nav className={isMobile ? 'mobile' : 'desktop'} />
320
+ }
321
+ ```
322
+
323
+ **正确示例(仅布尔值切换时重渲染):**
324
+
325
+ ```tsx
326
+ function Sidebar() {
327
+ const isMobile = useMediaQuery('(max-width: 767px)')
328
+ return <nav className={isMobile ? 'mobile' : 'desktop'} />
329
+ }
330
+ ```
331
+
332
+ ---
333
+
334
+ ## 规则 5.9:使用函数式 setState 更新
335
+
336
+ **影响:** 中 (MEDIUM)
337
+ **标签:** react, hooks, useState, useCallback, callbacks, closures
338
+
339
+ ## 使用函数式 setState 更新
340
+
341
+ 当状态更新依赖当前状态值时,应使用 setState 的函数式写法,而不是直接引用外层状态变量。这样可以避免过期闭包、减少不必要依赖,并让回调引用更稳定。
342
+
343
+ **错误示例(必须把 state 放进依赖):**
344
+
345
+ ```tsx
346
+ function TodoList() {
347
+ const [items, setItems] = useState(initialItems)
348
+
349
+ // 回调必须依赖 items,items 每次变化都会重建
350
+ const addItems = useCallback((newItems: Item[]) => {
351
+ setItems([...items, ...newItems])
352
+ }, [items]) // ❌ 依赖 items 会导致频繁重建
353
+
354
+ // 若漏掉依赖会有过期闭包风险
355
+ const removeItem = useCallback((id: string) => {
356
+ setItems(items.filter(item => item.id !== id))
357
+ }, []) // ❌ 缺少 items 依赖,会读取过期 items
358
+
359
+ return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
360
+ }
361
+ ```
362
+
363
+ 第一个回调会在 `items` 每次变化时重建,可能导致子组件不必要重渲染。第二个回调存在过期闭包缺陷,会始终引用初始 `items` 值。
364
+
365
+ **正确示例(回调稳定、无过期闭包):**
366
+
367
+ ```tsx
368
+ function TodoList() {
369
+ const [items, setItems] = useState(initialItems)
370
+
371
+ // 稳定回调,不会被重建
372
+ const addItems = useCallback((newItems: Item[]) => {
373
+ setItems(curr => [...curr, ...newItems])
374
+ }, []) // ✅ 无需依赖
375
+
376
+ // 总能读取最新状态,无过期闭包风险
377
+ const removeItem = useCallback((id: string) => {
378
+ setItems(curr => curr.filter(item => item.id !== id))
379
+ }, []) // ✅ 安全且稳定
380
+
381
+ return <ItemsEditor items={items} onAdd={addItems} onRemove={removeItem} />
382
+ }
383
+ ```
384
+
385
+ **收益:**
386
+
387
+ 1. **回调引用稳定**:state 变化时不必重建回调
388
+ 2. **避免过期闭包**:始终基于最新状态计算
389
+ 3. **减少依赖复杂度**:依赖数组更简洁,降低隐性问题
390
+ 4. **减少 Bug**:规避常见 React 闭包问题来源
391
+
392
+ **适用场景:**
393
+
394
+ - 任何依赖当前 state 的 setState 更新
395
+ - 在 useCallback/useMemo 中需要读取 state
396
+ - 事件处理器内引用 state
397
+ - 异步流程里更新 state
398
+
399
+ **可直接赋值的场景:**
400
+
401
+ - 设置静态值:`setCount(0)`
402
+ - 仅由 props/参数得出:`setName(newName)`
403
+ - 更新不依赖前值
404
+
405
+ **说明:** 即使启用 [React Compiler](https://react.dev/learn/react-compiler) 可自动优化部分场景,函数式更新仍推荐用于保证正确性并避免过期闭包。
406
+
407
+ ---
408
+
409
+ ## 规则 5.10:使用惰性状态初始化
410
+
411
+ **影响:** 中 (MEDIUM)
412
+ **标签:** react, hooks, useState, performance, initialization
413
+
414
+ ## 使用惰性状态初始化
415
+
416
+ 对于初始化开销较大的 state,应向 `useState` 传函数。若直接传表达式,初始化逻辑会在每次渲染时执行,尽管初始化值只会被用一次。
417
+
418
+ **错误示例(每次渲染都会运行):**
419
+
420
+ ```tsx
421
+ function FilteredList({ items }: { items: Item[] }) {
422
+ // buildSearchIndex() 每次渲染都会执行,即使初始化早已完成
423
+ const [searchIndex, setSearchIndex] = useState(buildSearchIndex(items))
424
+ const [query, setQuery] = useState('')
425
+
426
+ // query 变化时,buildSearchIndex 仍会被不必要地再次执行
427
+ return <SearchResults index={searchIndex} query={query} />
428
+ }
429
+
430
+ function UserProfile() {
431
+ // JSON.parse 每次渲染都会执行
432
+ const [settings, setSettings] = useState(
433
+ JSON.parse(localStorage.getItem('settings') || '{}')
434
+ )
435
+
436
+ return <SettingsForm settings={settings} onChange={setSettings} />
437
+ }
438
+ ```
439
+
440
+ **正确示例(只执行一次):**
441
+
442
+ ```tsx
443
+ function FilteredList({ items }: { items: Item[] }) {
444
+ // buildSearchIndex() 仅在首次渲染执行
445
+ const [searchIndex, setSearchIndex] = useState(() => buildSearchIndex(items))
446
+ const [query, setQuery] = useState('')
447
+
448
+ return <SearchResults index={searchIndex} query={query} />
449
+ }
450
+
451
+ function UserProfile() {
452
+ // JSON.parse 仅在首次渲染执行
453
+ const [settings, setSettings] = useState(() => {
454
+ const stored = localStorage.getItem('settings')
455
+ return stored ? JSON.parse(stored) : {}
456
+ })
457
+
458
+ return <SettingsForm settings={settings} onChange={setSettings} />
459
+ }
460
+ ```
461
+
462
+ 当初始值来自 localStorage/sessionStorage、需要构建索引/映射等数据结构、读取 DOM 或做重计算转换时,优先用惰性初始化。
463
+
464
+ 对于简单原始值(`useState(0)`)、直接引用(`useState(props.value)`)或廉价字面量(`useState({})`),无需函数形式。
465
+
466
+ ---
467
+
468
+ ## 规则 5.11:对非紧急更新使用 Transition
469
+
470
+ **影响:** 中 (MEDIUM)
471
+ **标签:** rerender, transitions, startTransition, performance
472
+
473
+ ## 对非紧急更新使用 Transition
474
+
475
+ 将高频但非紧急的状态更新标记为 transition,保持界面响应流畅。
476
+
477
+ **错误示例(每次滚动都阻塞 UI):**
478
+
479
+ ```tsx
480
+ function ScrollTracker() {
481
+ const [scrollY, setScrollY] = useState(0)
482
+ useEffect(() => {
483
+ const handler = () => setScrollY(window.scrollY)
484
+ window.addEventListener('scroll', handler, { passive: true })
485
+ return () => window.removeEventListener('scroll', handler)
486
+ }, [])
487
+ }
488
+ ```
489
+
490
+ **正确示例(非阻塞更新):**
491
+
492
+ ```tsx
493
+ import { startTransition } from 'react'
494
+
495
+ function ScrollTracker() {
496
+ const [scrollY, setScrollY] = useState(0)
497
+ useEffect(() => {
498
+ const handler = () => {
499
+ startTransition(() => setScrollY(window.scrollY))
500
+ }
501
+ window.addEventListener('scroll', handler, { passive: true })
502
+ return () => window.removeEventListener('scroll', handler)
503
+ }, [])
504
+ }
505
+ ```
506
+
507
+ ---
508
+
509
+ ## 规则 5.12:临时值使用 useRef
510
+
511
+ **影响:** 中 (MEDIUM)
512
+ **标签:** rerender, useref, state, performance
513
+
514
+ ## 临时值使用 useRef
515
+
516
+ 当某个值变化频繁,且你不希望每次变化都触发重渲染(例如鼠标追踪、定时器、瞬时标记)时,应使用 `useRef`,而不是 `useState`。组件 state 用于驱动 UI,ref 用于临时、贴近 DOM 的值。更新 ref 不会触发重渲染。
517
+
518
+ **错误示例(每次更新都重渲染):**
519
+
520
+ ```tsx
521
+ function Tracker() {
522
+ const [lastX, setLastX] = useState(0)
523
+
524
+ useEffect(() => {
525
+ const onMove = (e: MouseEvent) => setLastX(e.clientX)
526
+ window.addEventListener('mousemove', onMove)
527
+ return () => window.removeEventListener('mousemove', onMove)
528
+ }, [])
529
+
530
+ return (
531
+ <div
532
+ style={{
533
+ position: 'fixed',
534
+ top: 0,
535
+ left: lastX,
536
+ width: 8,
537
+ height: 8,
538
+ background: 'black',
539
+ }}
540
+ />
541
+ )
542
+ }
543
+ ```
544
+
545
+
546
+
547
+ **正确示例(追踪更新不触发重渲染):**
548
+
549
+ ```tsx
550
+ function Tracker() {
551
+ const lastXRef = useRef(0)
552
+ const dotRef = useRef<HTMLDivElement>(null)
553
+
554
+ useEffect(() => {
555
+ const onMove = (e: MouseEvent) => {
556
+ lastXRef.current = e.clientX
557
+ const node = dotRef.current
558
+ if (node) {
559
+ node.style.transform = `translateX(${e.clientX}px)`
560
+ }
561
+ }
562
+ window.addEventListener('mousemove', onMove)
563
+ return () => window.removeEventListener('mousemove', onMove)
564
+ }, [])
565
+
566
+ return (
567
+ <div
568
+ ref={dotRef}
569
+ style={{
570
+ position: 'fixed',
571
+ top: 0,
572
+ left: 0,
573
+ width: 8,
574
+ height: 8,
575
+ background: 'black',
576
+ transform: 'translateX(0px)',
577
+ }}
578
+ />
579
+ )
580
+ }
581
+ ```