ccjk 14.1.10 → 14.2.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/dist/chunks/agent-teams.mjs +1 -1
- package/dist/chunks/api-cli.mjs +1 -1
- package/dist/chunks/api-config-selector.mjs +3 -3
- package/dist/chunks/ccjk-all.mjs +1 -1
- package/dist/chunks/ccjk-mcp.mjs +1 -1
- package/dist/chunks/ccjk-setup.mjs +1 -1
- package/dist/chunks/ccr.mjs +8 -8
- package/dist/chunks/check-updates.mjs +1 -1
- package/dist/chunks/claude-code-incremental-manager.mjs +3 -3
- package/dist/chunks/claude-config.mjs +1 -1
- package/dist/chunks/codex-config-switch.mjs +1 -1
- package/dist/chunks/codex-provider-manager.mjs +1 -1
- package/dist/chunks/config-switch.mjs +1 -1
- package/dist/chunks/config.mjs +19 -3
- package/dist/chunks/config2.mjs +1 -1
- package/dist/chunks/config3.mjs +1 -1
- package/dist/chunks/doctor.mjs +175 -6
- package/dist/chunks/features.mjs +3 -3
- package/dist/chunks/index10.mjs +19 -5
- package/dist/chunks/init.mjs +5 -5
- package/dist/chunks/mcp-cli.mjs +3 -3
- package/dist/chunks/mcp.mjs +3 -3
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/quick-provider.mjs +1 -1
- package/dist/chunks/quick-setup.mjs +5 -5
- package/dist/chunks/simple-config.mjs +1 -1
- package/dist/chunks/smart-guide.mjs +1 -1
- package/dist/chunks/update.mjs +6 -6
- package/dist/chunks/zero-config.mjs +7 -4
- package/dist/cli.mjs +0 -0
- package/dist/index.d.mts +3 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +1 -1
- package/dist/shared/{ccjk.BJ3Zpjo5.mjs → ccjk.BCzOWT1L.mjs} +3 -2
- package/dist/shared/{ccjk.B8oqkakg.mjs → ccjk.BLsIiTqO.mjs} +1 -1
- package/dist/shared/{ccjk.Hwoicrh8.mjs → ccjk.BXv8aYs1.mjs} +1 -1
- package/dist/shared/{ccjk.B9OuS4xZ.mjs → ccjk.CfKJnpbB.mjs} +1 -1
- package/dist/shared/{ccjk.BzxpiEPF.mjs → ccjk.Cgv_cFVX.mjs} +2 -2
- package/dist/shared/{ccjk.Di1IYU3u.mjs → ccjk.DDL-4C-k.mjs} +47 -10
- package/dist/shared/{ccjk.BEiR3L4C.mjs → ccjk.DOw7Fawt.mjs} +3 -3
- package/dist/shared/{ccjk.tI_s2uSh.mjs → ccjk.f3TBLJSt.mjs} +1 -1
- package/dist/templates/agents/README.md +78 -0
- package/dist/templates/common/error-prevention.md +267 -0
- package/dist/templates/common/karpathy-baseline.md +83 -0
- package/dist/templates/common/output-styles/zh-CN/carmack-mode.md +381 -0
- package/dist/templates/common/output-styles/zh-CN/dhh-mode.md +265 -0
- package/dist/templates/common/output-styles/zh-CN/evan-you-mode.md +539 -0
- package/dist/templates/common/output-styles/zh-CN/jobs-mode.md +369 -0
- package/dist/templates/common/output-styles/zh-CN/linus-mode.md +135 -0
- package/dist/templates/common/output-styles/zh-CN/uncle-bob-mode.md +221 -0
- package/dist/templates/common/workflow/continuousDelivery/en/continuous-delivery.md +628 -0
- package/dist/templates/common/workflow/continuousDelivery/zh-CN/continuous-delivery.md +628 -0
- package/dist/templates/common/workflow/essential/en/agents/ccjk-config-agent.md +187 -0
- package/dist/templates/common/workflow/essential/en/agents/ccjk-mcp-agent.md +191 -0
- package/dist/templates/common/workflow/essential/en/agents/ccjk-skill-agent.md +249 -0
- package/dist/templates/common/workflow/essential/en/agents/ccjk-workflow-agent.md +277 -0
- package/dist/templates/common/workflow/essential/en/agents/get-current-datetime.md +29 -0
- package/dist/templates/common/workflow/essential/en/agents/init-architect.md +115 -0
- package/dist/templates/common/workflow/essential/en/agents/ui-ux-designer.md +91 -0
- package/dist/templates/common/workflow/essential/en/feat.md +92 -0
- package/dist/templates/common/workflow/essential/en/goal.md +147 -0
- package/dist/templates/common/workflow/essential/en/init-project.md +53 -0
- package/dist/templates/common/workflow/essential/zh-CN/agents/get-current-datetime.md +29 -0
- package/dist/templates/common/workflow/essential/zh-CN/agents/init-architect.md +115 -0
- package/dist/templates/common/workflow/essential/zh-CN/agents/ui-ux-designer.md +91 -0
- package/dist/templates/common/workflow/essential/zh-CN/feat.md +315 -0
- package/dist/templates/common/workflow/essential/zh-CN/goal.md +146 -0
- package/dist/templates/common/workflow/essential/zh-CN/init-project.md +53 -0
- package/dist/templates/common/workflow/git/en/git-cleanBranches.md +102 -0
- package/dist/templates/common/workflow/git/en/git-commit.md +205 -0
- package/dist/templates/common/workflow/git/en/git-rollback.md +90 -0
- package/dist/templates/common/workflow/git/en/git-worktree.md +276 -0
- package/dist/templates/common/workflow/git/zh-CN/git-cleanBranches.md +102 -0
- package/dist/templates/common/workflow/git/zh-CN/git-commit.md +205 -0
- package/dist/templates/common/workflow/git/zh-CN/git-rollback.md +90 -0
- package/dist/templates/common/workflow/git/zh-CN/git-worktree.md +276 -0
- package/dist/templates/common/workflow/interview/en/interview.md +67 -0
- package/dist/templates/common/workflow/interview/zh-CN/interview.md +67 -0
- package/dist/templates/common/workflow/linearMethod/en/linear-method.md +651 -0
- package/dist/templates/common/workflow/linearMethod/zh-CN/linear-method.md +752 -0
- package/dist/templates/common/workflow/refactoringMaster/en/refactoring-master.md +516 -0
- package/dist/templates/common/workflow/refactoringMaster/zh-CN/refactoring-master.md +812 -0
- package/dist/templates/common/workflow/sixStep/en/workflow.md +83 -0
- package/dist/templates/common/workflow/sixStep/zh-CN/workflow.md +359 -0
- package/dist/templates/common/workflow/specFirstTDD/en/spec-first-tdd.md +364 -0
- package/dist/templates/common/workflow/specFirstTDD/zh-CN/spec-first-tdd.md +366 -0
- package/dist/templates/hooks/README.md +212 -0
- package/dist/templates/hooks/git-workflow-hooks.md +551 -0
- package/dist/templates/hooks/post-test-coverage.md +434 -0
- package/dist/templates/hooks/pre-commit-black.md +274 -0
- package/dist/templates/hooks/pre-commit-eslint.md +153 -0
- package/dist/templates/hooks/pre-commit-gofmt.md +284 -0
- package/dist/templates/hooks/pre-commit-prettier.md +212 -0
- package/dist/templates/hooks/pre-commit-type-check.md +377 -0
- package/dist/templates/skills/ccjk-init.md +154 -0
- package/dist/templates/skills/ccjk-mcp-setup.md +205 -0
- package/dist/templates/skills/ccjk-troubleshoot.md +228 -0
- package/dist/templates/skills/django-patterns.md +1016 -0
- package/dist/templates/skills/git-workflow.md +748 -0
- package/dist/templates/skills/go-idioms.md +963 -0
- package/dist/templates/skills/nextjs-optimization.md +694 -0
- package/dist/templates/skills/python-pep8.md +852 -0
- package/dist/templates/skills/react-patterns.md +686 -0
- package/dist/templates/skills/rust-patterns.md +1057 -0
- package/dist/templates/skills/security-best-practices.md +1413 -0
- package/dist/templates/skills/testing-best-practices.md +1315 -0
- package/dist/templates/skills/ts-best-practices.md +354 -0
- package/package.json +40 -43
- package/templates/common/karpathy-baseline.md +83 -0
- package/templates/common/output-styles/zh-CN/carmack-mode.md +14 -0
- package/templates/common/output-styles/zh-CN/dhh-mode.md +14 -0
- package/templates/common/output-styles/zh-CN/evan-you-mode.md +14 -0
- package/templates/common/output-styles/zh-CN/jobs-mode.md +14 -0
- package/templates/common/output-styles/zh-CN/linus-mode.md +14 -0
- package/templates/common/output-styles/zh-CN/uncle-bob-mode.md +14 -0
- package/templates/common/workflow/linearMethod/zh-CN/linear-method.md +2 -0
- package/templates/common/workflow/refactoringMaster/zh-CN/refactoring-master.md +2 -0
- package/templates/common/workflow/sixStep/zh-CN/workflow.md +2 -0
- package/templates/common/workflow/specFirstTDD/zh-CN/spec-first-tdd.md +2 -0
|
@@ -0,0 +1,686 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: react-patterns
|
|
3
|
+
description: React 18+ hooks, patterns, and performance optimization techniques
|
|
4
|
+
description_zh: React 18+ 钩子、模式和性能优化技术
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
category: frontend
|
|
7
|
+
triggers: ['/react-patterns', '/react', '/hooks', '/react-performance']
|
|
8
|
+
use_when:
|
|
9
|
+
- Building React 18+ applications with modern patterns
|
|
10
|
+
- Implementing custom hooks and component composition
|
|
11
|
+
- Optimizing React performance and bundle size
|
|
12
|
+
- Code review for React components
|
|
13
|
+
use_when_zh:
|
|
14
|
+
- 使用现代模式构建 React 18+ 应用程序
|
|
15
|
+
- 实现自定义钩子和组件组合
|
|
16
|
+
- 优化 React 性能和包大小
|
|
17
|
+
- React 组件代码审查
|
|
18
|
+
auto_activate: true
|
|
19
|
+
priority: 8
|
|
20
|
+
agents: [react-expert, frontend-architect]
|
|
21
|
+
tags: [react, hooks, performance, patterns, components]
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
# React Patterns | React 模式
|
|
25
|
+
|
|
26
|
+
## Context | 上下文
|
|
27
|
+
|
|
28
|
+
Use this skill when developing React 18+ applications that require modern patterns, optimal performance, and maintainable component architecture. Essential for scalable React development.
|
|
29
|
+
|
|
30
|
+
在开发需要现代模式、最佳性能和可维护组件架构的 React 18+ 应用程序时使用此技能。对于可扩展的 React 开发至关重要。
|
|
31
|
+
|
|
32
|
+
## Best Practices | 最佳实践
|
|
33
|
+
|
|
34
|
+
### 1. Modern Hook Patterns | 现代钩子模式
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
// ✅ Good: Custom hook with proper cleanup
|
|
38
|
+
function useApi<T>(url: string) {
|
|
39
|
+
const [data, setData] = useState<T | null>(null);
|
|
40
|
+
const [loading, setLoading] = useState(true);
|
|
41
|
+
const [error, setError] = useState<Error | null>(null);
|
|
42
|
+
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
const controller = new AbortController();
|
|
45
|
+
|
|
46
|
+
async function fetchData() {
|
|
47
|
+
try {
|
|
48
|
+
setLoading(true);
|
|
49
|
+
const response = await fetch(url, {
|
|
50
|
+
signal: controller.signal
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
if (!response.ok) {
|
|
54
|
+
throw new Error(`HTTP ${response.status}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const result = await response.json();
|
|
58
|
+
setData(result);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
if (err instanceof Error && err.name !== 'AbortError') {
|
|
61
|
+
setError(err);
|
|
62
|
+
}
|
|
63
|
+
} finally {
|
|
64
|
+
setLoading(false);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fetchData();
|
|
69
|
+
|
|
70
|
+
return () => controller.abort();
|
|
71
|
+
}, [url]);
|
|
72
|
+
|
|
73
|
+
return { data, loading, error };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ✅ Good: Compound hook pattern
|
|
77
|
+
function useLocalStorage<T>(key: string, initialValue: T) {
|
|
78
|
+
const [storedValue, setStoredValue] = useState<T>(() => {
|
|
79
|
+
try {
|
|
80
|
+
const item = window.localStorage.getItem(key);
|
|
81
|
+
return item ? JSON.parse(item) : initialValue;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
console.warn(`Error reading localStorage key "${key}":`, error);
|
|
84
|
+
return initialValue;
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const setValue = useCallback((value: T | ((val: T) => T)) => {
|
|
89
|
+
try {
|
|
90
|
+
const valueToStore = value instanceof Function ? value(storedValue) : value;
|
|
91
|
+
setStoredValue(valueToStore);
|
|
92
|
+
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
|
93
|
+
} catch (error) {
|
|
94
|
+
console.warn(`Error setting localStorage key "${key}":`, error);
|
|
95
|
+
}
|
|
96
|
+
}, [key, storedValue]);
|
|
97
|
+
|
|
98
|
+
return [storedValue, setValue] as const;
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 2. Component Composition Patterns | 组件组合模式
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// ✅ Good: Compound component pattern
|
|
106
|
+
interface TabsContextType {
|
|
107
|
+
activeTab: string;
|
|
108
|
+
setActiveTab: (tab: string) => void;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const TabsContext = createContext<TabsContextType | null>(null);
|
|
112
|
+
|
|
113
|
+
function Tabs({ children, defaultTab }: { children: React.ReactNode; defaultTab: string }) {
|
|
114
|
+
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<TabsContext.Provider value={{ activeTab, setActiveTab }}>
|
|
118
|
+
<div className="tabs">{children}</div>
|
|
119
|
+
</TabsContext.Provider>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function TabList({ children }: { children: React.ReactNode }) {
|
|
124
|
+
return <div className="tab-list" role="tablist">{children}</div>;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function Tab({ id, children }: { id: string; children: React.ReactNode }) {
|
|
128
|
+
const context = useContext(TabsContext);
|
|
129
|
+
if (!context) throw new Error('Tab must be used within Tabs');
|
|
130
|
+
|
|
131
|
+
const { activeTab, setActiveTab } = context;
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<button
|
|
135
|
+
role="tab"
|
|
136
|
+
aria-selected={activeTab === id}
|
|
137
|
+
onClick={() => setActiveTab(id)}
|
|
138
|
+
className={`tab ${activeTab === id ? 'active' : ''}`}
|
|
139
|
+
>
|
|
140
|
+
{children}
|
|
141
|
+
</button>
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function TabPanels({ children }: { children: React.ReactNode }) {
|
|
146
|
+
return <div className="tab-panels">{children}</div>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function TabPanel({ id, children }: { id: string; children: React.ReactNode }) {
|
|
150
|
+
const context = useContext(TabsContext);
|
|
151
|
+
if (!context) throw new Error('TabPanel must be used within Tabs');
|
|
152
|
+
|
|
153
|
+
const { activeTab } = context;
|
|
154
|
+
|
|
155
|
+
if (activeTab !== id) return null;
|
|
156
|
+
|
|
157
|
+
return (
|
|
158
|
+
<div role="tabpanel" className="tab-panel">
|
|
159
|
+
{children}
|
|
160
|
+
</div>
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Attach sub-components
|
|
165
|
+
Tabs.List = TabList;
|
|
166
|
+
Tabs.Tab = Tab;
|
|
167
|
+
Tabs.Panels = TabPanels;
|
|
168
|
+
Tabs.Panel = TabPanel;
|
|
169
|
+
|
|
170
|
+
// Usage
|
|
171
|
+
function App() {
|
|
172
|
+
return (
|
|
173
|
+
<Tabs defaultTab="tab1">
|
|
174
|
+
<Tabs.List>
|
|
175
|
+
<Tabs.Tab id="tab1">Tab 1</Tabs.Tab>
|
|
176
|
+
<Tabs.Tab id="tab2">Tab 2</Tabs.Tab>
|
|
177
|
+
</Tabs.List>
|
|
178
|
+
<Tabs.Panels>
|
|
179
|
+
<Tabs.Panel id="tab1">Content 1</Tabs.Panel>
|
|
180
|
+
<Tabs.Panel id="tab2">Content 2</Tabs.Panel>
|
|
181
|
+
</Tabs.Panels>
|
|
182
|
+
</Tabs>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### 3. Render Props and Higher-Order Components | 渲染属性和高阶组件
|
|
188
|
+
|
|
189
|
+
```tsx
|
|
190
|
+
// ✅ Good: Render props pattern
|
|
191
|
+
interface MousePosition {
|
|
192
|
+
x: number;
|
|
193
|
+
y: number;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function MouseTracker({
|
|
197
|
+
children
|
|
198
|
+
}: {
|
|
199
|
+
children: (position: MousePosition) => React.ReactNode
|
|
200
|
+
}) {
|
|
201
|
+
const [position, setPosition] = useState<MousePosition>({ x: 0, y: 0 });
|
|
202
|
+
|
|
203
|
+
useEffect(() => {
|
|
204
|
+
function handleMouseMove(event: MouseEvent) {
|
|
205
|
+
setPosition({ x: event.clientX, y: event.clientY });
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
209
|
+
return () => document.removeEventListener('mousemove', handleMouseMove);
|
|
210
|
+
}, []);
|
|
211
|
+
|
|
212
|
+
return <>{children(position)}</>;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Usage
|
|
216
|
+
function App() {
|
|
217
|
+
return (
|
|
218
|
+
<MouseTracker>
|
|
219
|
+
{({ x, y }) => (
|
|
220
|
+
<div>Mouse position: {x}, {y}</div>
|
|
221
|
+
)}
|
|
222
|
+
</MouseTracker>
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// ✅ Good: HOC with proper TypeScript support
|
|
227
|
+
function withLoading<P extends object>(
|
|
228
|
+
Component: React.ComponentType<P>
|
|
229
|
+
) {
|
|
230
|
+
return function WithLoadingComponent(
|
|
231
|
+
props: P & { loading?: boolean }
|
|
232
|
+
) {
|
|
233
|
+
const { loading, ...restProps } = props;
|
|
234
|
+
|
|
235
|
+
if (loading) {
|
|
236
|
+
return <div className="loading">Loading...</div>;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return <Component {...(restProps as P)} />;
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Usage
|
|
244
|
+
const UserListWithLoading = withLoading(UserList);
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Performance Optimization | 性能优化
|
|
248
|
+
|
|
249
|
+
### 1. Memoization Patterns | 记忆化模式
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
252
|
+
// ✅ Good: Proper useMemo usage
|
|
253
|
+
function ExpensiveComponent({ items, filter }: { items: Item[]; filter: string }) {
|
|
254
|
+
const filteredItems = useMemo(() => {
|
|
255
|
+
return items.filter(item =>
|
|
256
|
+
item.name.toLowerCase().includes(filter.toLowerCase())
|
|
257
|
+
);
|
|
258
|
+
}, [items, filter]);
|
|
259
|
+
|
|
260
|
+
const expensiveValue = useMemo(() => {
|
|
261
|
+
return filteredItems.reduce((sum, item) => sum + item.value, 0);
|
|
262
|
+
}, [filteredItems]);
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<div>
|
|
266
|
+
<div>Total: {expensiveValue}</div>
|
|
267
|
+
{filteredItems.map(item => (
|
|
268
|
+
<ItemComponent key={item.id} item={item} />
|
|
269
|
+
))}
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// ✅ Good: useCallback for event handlers
|
|
275
|
+
function TodoList({ todos, onToggle, onDelete }: TodoListProps) {
|
|
276
|
+
const handleToggle = useCallback((id: string) => {
|
|
277
|
+
onToggle(id);
|
|
278
|
+
}, [onToggle]);
|
|
279
|
+
|
|
280
|
+
const handleDelete = useCallback((id: string) => {
|
|
281
|
+
onDelete(id);
|
|
282
|
+
}, [onDelete]);
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<ul>
|
|
286
|
+
{todos.map(todo => (
|
|
287
|
+
<TodoItem
|
|
288
|
+
key={todo.id}
|
|
289
|
+
todo={todo}
|
|
290
|
+
onToggle={handleToggle}
|
|
291
|
+
onDelete={handleDelete}
|
|
292
|
+
/>
|
|
293
|
+
))}
|
|
294
|
+
</ul>
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ✅ Good: React.memo with custom comparison
|
|
299
|
+
const TodoItem = React.memo(function TodoItem({
|
|
300
|
+
todo,
|
|
301
|
+
onToggle,
|
|
302
|
+
onDelete
|
|
303
|
+
}: TodoItemProps) {
|
|
304
|
+
return (
|
|
305
|
+
<li>
|
|
306
|
+
<input
|
|
307
|
+
type="checkbox"
|
|
308
|
+
checked={todo.completed}
|
|
309
|
+
onChange={() => onToggle(todo.id)}
|
|
310
|
+
/>
|
|
311
|
+
<span>{todo.text}</span>
|
|
312
|
+
<button onClick={() => onDelete(todo.id)}>Delete</button>
|
|
313
|
+
</li>
|
|
314
|
+
);
|
|
315
|
+
}, (prevProps, nextProps) => {
|
|
316
|
+
return (
|
|
317
|
+
prevProps.todo.id === nextProps.todo.id &&
|
|
318
|
+
prevProps.todo.completed === nextProps.todo.completed &&
|
|
319
|
+
prevProps.todo.text === nextProps.todo.text
|
|
320
|
+
);
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### 2. Code Splitting and Lazy Loading | 代码分割和懒加载
|
|
325
|
+
|
|
326
|
+
```tsx
|
|
327
|
+
// ✅ Good: Route-based code splitting
|
|
328
|
+
const HomePage = lazy(() => import('./pages/HomePage'));
|
|
329
|
+
const AboutPage = lazy(() => import('./pages/AboutPage'));
|
|
330
|
+
const UserPage = lazy(() => import('./pages/UserPage'));
|
|
331
|
+
|
|
332
|
+
function App() {
|
|
333
|
+
return (
|
|
334
|
+
<Router>
|
|
335
|
+
<Suspense fallback={<div>Loading...</div>}>
|
|
336
|
+
<Routes>
|
|
337
|
+
<Route path="/" element={<HomePage />} />
|
|
338
|
+
<Route path="/about" element={<AboutPage />} />
|
|
339
|
+
<Route path="/user/:id" element={<UserPage />} />
|
|
340
|
+
</Routes>
|
|
341
|
+
</Suspense>
|
|
342
|
+
</Router>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ✅ Good: Component-based lazy loading
|
|
347
|
+
function LazyModal({ isOpen, onClose }: { isOpen: boolean; onClose: () => void }) {
|
|
348
|
+
const [ModalComponent, setModalComponent] = useState<React.ComponentType | null>(null);
|
|
349
|
+
|
|
350
|
+
useEffect(() => {
|
|
351
|
+
if (isOpen && !ModalComponent) {
|
|
352
|
+
import('./Modal').then(module => {
|
|
353
|
+
setModalComponent(() => module.default);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}, [isOpen, ModalComponent]);
|
|
357
|
+
|
|
358
|
+
if (!isOpen || !ModalComponent) return null;
|
|
359
|
+
|
|
360
|
+
return <ModalComponent onClose={onClose} />;
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### 3. State Management Patterns | 状态管理模式
|
|
365
|
+
|
|
366
|
+
```tsx
|
|
367
|
+
// ✅ Good: useReducer for complex state
|
|
368
|
+
interface TodoState {
|
|
369
|
+
todos: Todo[];
|
|
370
|
+
filter: 'all' | 'active' | 'completed';
|
|
371
|
+
loading: boolean;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
type TodoAction =
|
|
375
|
+
| { type: 'ADD_TODO'; payload: { text: string } }
|
|
376
|
+
| { type: 'TOGGLE_TODO'; payload: { id: string } }
|
|
377
|
+
| { type: 'DELETE_TODO'; payload: { id: string } }
|
|
378
|
+
| { type: 'SET_FILTER'; payload: { filter: TodoState['filter'] } }
|
|
379
|
+
| { type: 'SET_LOADING'; payload: { loading: boolean } };
|
|
380
|
+
|
|
381
|
+
function todoReducer(state: TodoState, action: TodoAction): TodoState {
|
|
382
|
+
switch (action.type) {
|
|
383
|
+
case 'ADD_TODO':
|
|
384
|
+
return {
|
|
385
|
+
...state,
|
|
386
|
+
todos: [
|
|
387
|
+
...state.todos,
|
|
388
|
+
{
|
|
389
|
+
id: crypto.randomUUID(),
|
|
390
|
+
text: action.payload.text,
|
|
391
|
+
completed: false,
|
|
392
|
+
createdAt: new Date()
|
|
393
|
+
}
|
|
394
|
+
]
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
case 'TOGGLE_TODO':
|
|
398
|
+
return {
|
|
399
|
+
...state,
|
|
400
|
+
todos: state.todos.map(todo =>
|
|
401
|
+
todo.id === action.payload.id
|
|
402
|
+
? { ...todo, completed: !todo.completed }
|
|
403
|
+
: todo
|
|
404
|
+
)
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
case 'DELETE_TODO':
|
|
408
|
+
return {
|
|
409
|
+
...state,
|
|
410
|
+
todos: state.todos.filter(todo => todo.id !== action.payload.id)
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
case 'SET_FILTER':
|
|
414
|
+
return {
|
|
415
|
+
...state,
|
|
416
|
+
filter: action.payload.filter
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
case 'SET_LOADING':
|
|
420
|
+
return {
|
|
421
|
+
...state,
|
|
422
|
+
loading: action.payload.loading
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
default:
|
|
426
|
+
return state;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function TodoApp() {
|
|
431
|
+
const [state, dispatch] = useReducer(todoReducer, {
|
|
432
|
+
todos: [],
|
|
433
|
+
filter: 'all',
|
|
434
|
+
loading: false
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const addTodo = useCallback((text: string) => {
|
|
438
|
+
dispatch({ type: 'ADD_TODO', payload: { text } });
|
|
439
|
+
}, []);
|
|
440
|
+
|
|
441
|
+
const toggleTodo = useCallback((id: string) => {
|
|
442
|
+
dispatch({ type: 'TOGGLE_TODO', payload: { id } });
|
|
443
|
+
}, []);
|
|
444
|
+
|
|
445
|
+
return (
|
|
446
|
+
<div>
|
|
447
|
+
<TodoInput onAdd={addTodo} />
|
|
448
|
+
<TodoList
|
|
449
|
+
todos={state.todos}
|
|
450
|
+
filter={state.filter}
|
|
451
|
+
onToggle={toggleTodo}
|
|
452
|
+
/>
|
|
453
|
+
</div>
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
## Anti-Patterns | 反模式
|
|
459
|
+
|
|
460
|
+
### 1. Avoid Unnecessary Re-renders | 避免不必要的重新渲染
|
|
461
|
+
|
|
462
|
+
```tsx
|
|
463
|
+
// ❌ Bad: Creating objects in render
|
|
464
|
+
function BadComponent({ items }: { items: Item[] }) {
|
|
465
|
+
return (
|
|
466
|
+
<div>
|
|
467
|
+
{items.map(item => (
|
|
468
|
+
<ItemComponent
|
|
469
|
+
key={item.id}
|
|
470
|
+
item={item}
|
|
471
|
+
style={{ color: 'red' }} // New object every render!
|
|
472
|
+
onClick={() => console.log(item.id)} // New function every render!
|
|
473
|
+
/>
|
|
474
|
+
))}
|
|
475
|
+
</div>
|
|
476
|
+
);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// ✅ Good: Stable references
|
|
480
|
+
const itemStyle = { color: 'red' };
|
|
481
|
+
|
|
482
|
+
function GoodComponent({ items }: { items: Item[] }) {
|
|
483
|
+
const handleClick = useCallback((id: string) => {
|
|
484
|
+
console.log(id);
|
|
485
|
+
}, []);
|
|
486
|
+
|
|
487
|
+
return (
|
|
488
|
+
<div>
|
|
489
|
+
{items.map(item => (
|
|
490
|
+
<ItemComponent
|
|
491
|
+
key={item.id}
|
|
492
|
+
item={item}
|
|
493
|
+
style={itemStyle}
|
|
494
|
+
onClick={() => handleClick(item.id)}
|
|
495
|
+
/>
|
|
496
|
+
))}
|
|
497
|
+
</div>
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
### 2. Avoid Prop Drilling | 避免属性钻取
|
|
503
|
+
|
|
504
|
+
```tsx
|
|
505
|
+
// ❌ Bad: Prop drilling
|
|
506
|
+
function App() {
|
|
507
|
+
const [user, setUser] = useState<User | null>(null);
|
|
508
|
+
|
|
509
|
+
return <Layout user={user} setUser={setUser} />;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
function Layout({ user, setUser }: { user: User | null; setUser: (user: User | null) => void }) {
|
|
513
|
+
return (
|
|
514
|
+
<div>
|
|
515
|
+
<Header user={user} setUser={setUser} />
|
|
516
|
+
<Main user={user} />
|
|
517
|
+
</div>
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function Header({ user, setUser }: { user: User | null; setUser: (user: User | null) => void }) {
|
|
522
|
+
return <UserMenu user={user} setUser={setUser} />;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// ✅ Good: Context API
|
|
526
|
+
const UserContext = createContext<{
|
|
527
|
+
user: User | null;
|
|
528
|
+
setUser: (user: User | null) => void;
|
|
529
|
+
} | null>(null);
|
|
530
|
+
|
|
531
|
+
function UserProvider({ children }: { children: React.ReactNode }) {
|
|
532
|
+
const [user, setUser] = useState<User | null>(null);
|
|
533
|
+
|
|
534
|
+
return (
|
|
535
|
+
<UserContext.Provider value={{ user, setUser }}>
|
|
536
|
+
{children}
|
|
537
|
+
</UserContext.Provider>
|
|
538
|
+
);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
function useUser() {
|
|
542
|
+
const context = useContext(UserContext);
|
|
543
|
+
if (!context) {
|
|
544
|
+
throw new Error('useUser must be used within UserProvider');
|
|
545
|
+
}
|
|
546
|
+
return context;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function App() {
|
|
550
|
+
return (
|
|
551
|
+
<UserProvider>
|
|
552
|
+
<Layout />
|
|
553
|
+
</UserProvider>
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
function UserMenu() {
|
|
558
|
+
const { user, setUser } = useUser();
|
|
559
|
+
// Use user and setUser directly
|
|
560
|
+
}
|
|
561
|
+
```
|
|
562
|
+
|
|
563
|
+
### 3. Avoid Mutating State | 避免状态突变
|
|
564
|
+
|
|
565
|
+
```tsx
|
|
566
|
+
// ❌ Bad: Mutating state directly
|
|
567
|
+
function BadTodoList() {
|
|
568
|
+
const [todos, setTodos] = useState<Todo[]>([]);
|
|
569
|
+
|
|
570
|
+
const addTodo = (text: string) => {
|
|
571
|
+
todos.push({ id: Date.now(), text, completed: false }); // Mutation!
|
|
572
|
+
setTodos(todos); // Won't trigger re-render
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
const toggleTodo = (id: number) => {
|
|
576
|
+
const todo = todos.find(t => t.id === id);
|
|
577
|
+
if (todo) {
|
|
578
|
+
todo.completed = !todo.completed; // Mutation!
|
|
579
|
+
setTodos(todos); // Won't trigger re-render
|
|
580
|
+
}
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
return (
|
|
584
|
+
<div>
|
|
585
|
+
{todos.map(todo => (
|
|
586
|
+
<div key={todo.id} onClick={() => toggleTodo(todo.id)}>
|
|
587
|
+
{todo.text}
|
|
588
|
+
</div>
|
|
589
|
+
))}
|
|
590
|
+
</div>
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// ✅ Good: Immutable updates
|
|
595
|
+
function GoodTodoList() {
|
|
596
|
+
const [todos, setTodos] = useState<Todo[]>([]);
|
|
597
|
+
|
|
598
|
+
const addTodo = useCallback((text: string) => {
|
|
599
|
+
setTodos(prev => [
|
|
600
|
+
...prev,
|
|
601
|
+
{ id: Date.now(), text, completed: false }
|
|
602
|
+
]);
|
|
603
|
+
}, []);
|
|
604
|
+
|
|
605
|
+
const toggleTodo = useCallback((id: number) => {
|
|
606
|
+
setTodos(prev => prev.map(todo =>
|
|
607
|
+
todo.id === id
|
|
608
|
+
? { ...todo, completed: !todo.completed }
|
|
609
|
+
: todo
|
|
610
|
+
));
|
|
611
|
+
}, []);
|
|
612
|
+
|
|
613
|
+
return (
|
|
614
|
+
<div>
|
|
615
|
+
{todos.map(todo => (
|
|
616
|
+
<div key={todo.id} onClick={() => toggleTodo(todo.id)}>
|
|
617
|
+
{todo.text}
|
|
618
|
+
</div>
|
|
619
|
+
))}
|
|
620
|
+
</div>
|
|
621
|
+
);
|
|
622
|
+
}
|
|
623
|
+
```
|
|
624
|
+
|
|
625
|
+
## Testing Patterns | 测试模式
|
|
626
|
+
|
|
627
|
+
```tsx
|
|
628
|
+
// ✅ Good: Testing custom hooks
|
|
629
|
+
import { renderHook, act } from '@testing-library/react';
|
|
630
|
+
|
|
631
|
+
describe('useCounter', () => {
|
|
632
|
+
test('should increment counter', () => {
|
|
633
|
+
const { result } = renderHook(() => useCounter(0));
|
|
634
|
+
|
|
635
|
+
act(() => {
|
|
636
|
+
result.current.increment();
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
expect(result.current.count).toBe(1);
|
|
640
|
+
});
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// ✅ Good: Testing components with context
|
|
644
|
+
function renderWithUserContext(ui: React.ReactElement, { user = null } = {}) {
|
|
645
|
+
const Wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
646
|
+
<UserContext.Provider value={{ user, setUser: jest.fn() }}>
|
|
647
|
+
{children}
|
|
648
|
+
</UserContext.Provider>
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
return render(ui, { wrapper: Wrapper });
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
test('should display user name when logged in', () => {
|
|
655
|
+
const user = { id: '1', name: 'John Doe' };
|
|
656
|
+
renderWithUserContext(<UserProfile />, { user });
|
|
657
|
+
|
|
658
|
+
expect(screen.getByText('John Doe')).toBeInTheDocument();
|
|
659
|
+
});
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
## Performance Checklist | 性能检查清单
|
|
663
|
+
|
|
664
|
+
- [ ] Components are properly memoized with React.memo
|
|
665
|
+
- [ ] Expensive calculations use useMemo
|
|
666
|
+
- [ ] Event handlers use useCallback
|
|
667
|
+
- [ ] Large lists implement virtualization
|
|
668
|
+
- [ ] Images are optimized and lazy-loaded
|
|
669
|
+
- [ ] Code splitting is implemented for routes
|
|
670
|
+
- [ ] Bundle size is monitored and optimized
|
|
671
|
+
- [ ] Unnecessary re-renders are eliminated
|
|
672
|
+
- [ ] State is lifted only when necessary
|
|
673
|
+
- [ ] Context providers are optimized to prevent cascading updates
|
|
674
|
+
|
|
675
|
+
## 性能检查清单
|
|
676
|
+
|
|
677
|
+
- [ ] 组件使用 React.memo 正确记忆化
|
|
678
|
+
- [ ] 昂贵的计算使用 useMemo
|
|
679
|
+
- [ ] 事件处理程序使用 useCallback
|
|
680
|
+
- [ ] 大型列表实现虚拟化
|
|
681
|
+
- [ ] 图像经过优化并懒加载
|
|
682
|
+
- [ ] 为路由实现代码分割
|
|
683
|
+
- [ ] 监控和优化包大小
|
|
684
|
+
- [ ] 消除不必要的重新渲染
|
|
685
|
+
- [ ] 仅在必要时提升状态
|
|
686
|
+
- [ ] 优化上下文提供者以防止级联更新
|