ccbot-cli 2.0.0 → 2.1.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/LICENSE +21 -0
- package/bin/adapters/claude.js +150 -0
- package/bin/adapters/codex.js +439 -0
- package/bin/install.js +509 -349
- package/bin/lib/ccline.js +82 -0
- package/bin/lib/utils.js +87 -34
- package/bin/uninstall.js +48 -0
- package/config/AGENTS.md +630 -0
- package/config/CLAUDE.md +229 -20
- package/config/ccline/config.toml +161 -0
- package/config/codex-config.example.toml +22 -0
- package/config/settings.example.json +32 -0
- package/output-styles/abyss-cultivator.md +399 -0
- package/package.json +14 -5
- package/skills/SKILL.md +159 -0
- package/skills/domains/ai/SKILL.md +34 -0
- package/skills/domains/ai/agent-dev.md +242 -0
- package/skills/domains/ai/llm-security.md +288 -0
- package/skills/domains/ai/prompt-and-eval.md +279 -0
- package/skills/domains/ai/rag-system.md +542 -0
- package/skills/domains/architecture/SKILL.md +42 -0
- package/skills/domains/architecture/api-design.md +225 -0
- package/skills/domains/architecture/caching.md +299 -0
- package/skills/domains/architecture/cloud-native.md +285 -0
- package/skills/domains/architecture/message-queue.md +329 -0
- package/skills/domains/architecture/security-arch.md +297 -0
- package/skills/domains/data-engineering/SKILL.md +207 -0
- package/skills/domains/development/SKILL.md +46 -0
- package/skills/domains/development/cpp.md +246 -0
- package/skills/domains/development/go.md +323 -0
- package/skills/domains/development/java.md +277 -0
- package/skills/domains/development/python.md +288 -0
- package/skills/domains/development/rust.md +313 -0
- package/skills/domains/development/shell.md +313 -0
- package/skills/domains/development/typescript.md +277 -0
- package/skills/domains/devops/SKILL.md +39 -0
- package/skills/domains/devops/cost-optimization.md +272 -0
- package/skills/domains/devops/database.md +217 -0
- package/skills/domains/devops/devsecops.md +198 -0
- package/skills/domains/devops/git-workflow.md +181 -0
- package/skills/domains/devops/observability.md +280 -0
- package/skills/domains/devops/performance.md +336 -0
- package/skills/domains/devops/testing.md +283 -0
- package/skills/domains/frontend-design/SKILL.md +38 -0
- package/skills/domains/frontend-design/claymorphism/SKILL.md +119 -0
- package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
- package/skills/domains/frontend-design/component-patterns.md +202 -0
- package/skills/domains/frontend-design/engineering.md +287 -0
- package/skills/domains/frontend-design/glassmorphism/SKILL.md +140 -0
- package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
- package/skills/domains/frontend-design/liquid-glass/SKILL.md +137 -0
- package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
- package/skills/domains/frontend-design/neubrutalism/SKILL.md +143 -0
- package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
- package/skills/domains/frontend-design/state-management.md +680 -0
- package/skills/domains/frontend-design/ui-aesthetics.md +110 -0
- package/skills/domains/frontend-design/ux-principles.md +156 -0
- package/skills/domains/infrastructure/SKILL.md +200 -0
- package/skills/domains/mobile/SKILL.md +224 -0
- package/skills/domains/orchestration/SKILL.md +29 -0
- package/skills/domains/orchestration/multi-agent.md +263 -0
- package/skills/domains/security/SKILL.md +54 -0
- package/skills/domains/security/blue-team.md +436 -0
- package/skills/domains/security/code-audit.md +265 -0
- package/skills/domains/security/pentest.md +226 -0
- package/skills/domains/security/red-team.md +375 -0
- package/skills/domains/security/threat-intel.md +372 -0
- package/skills/domains/security/vuln-research.md +369 -0
- package/skills/orchestration/multi-agent/SKILL.md +493 -0
- package/skills/run_skill.js +129 -0
- package/skills/tools/gen-docs/SKILL.md +116 -0
- package/skills/tools/gen-docs/scripts/doc_generator.js +435 -0
- package/skills/tools/lib/shared.js +98 -0
- package/skills/tools/verify-change/SKILL.md +140 -0
- package/skills/tools/verify-change/scripts/change_analyzer.js +289 -0
- package/skills/tools/verify-module/SKILL.md +127 -0
- package/skills/tools/verify-module/scripts/module_scanner.js +171 -0
- package/skills/tools/verify-quality/SKILL.md +160 -0
- package/skills/tools/verify-quality/scripts/quality_checker.js +337 -0
- package/skills/tools/verify-security/SKILL.md +143 -0
- package/skills/tools/verify-security/scripts/security_scanner.js +283 -0
- package/bin/lib/registry.js +0 -61
- package/config/.claudeignore +0 -11
|
@@ -0,0 +1,680 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: state-management
|
|
3
|
+
description: 前端状态管理技术。Redux、Zustand、Jotai、Recoil、Context API、状态选择决策。当用户提到状态管理、Redux、Zustand、Jotai、Recoil、全局状态、状态同步时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 🎨 🗂️ 状态管理 · State Management
|
|
7
|
+
|
|
8
|
+
## 状态管理对比
|
|
9
|
+
|
|
10
|
+
| 框架 | 模式 | 学习曲线 | 性能 | 适用场景 |
|
|
11
|
+
|------|------|----------|------|----------|
|
|
12
|
+
| Redux | Flux | 陡峭 | 中 | 大型应用、复杂状态 |
|
|
13
|
+
| Zustand | Flux-like | 平缓 | 高 | 中小型应用、快速开发 |
|
|
14
|
+
| Jotai | Atomic | 平缓 | 高 | 细粒度更新、原子化状态 |
|
|
15
|
+
| Recoil | Atomic | 中等 | 高 | React生态、派生状态 |
|
|
16
|
+
| Context | Provider | 简单 | 低 | 简单共享、主题配置 |
|
|
17
|
+
| MobX | Reactive | 中等 | 高 | OOP风格、自动追踪 |
|
|
18
|
+
|
|
19
|
+
## 选择决策树
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
需要状态管理?
|
|
23
|
+
│
|
|
24
|
+
├─ 简单主题/配置 → Context API
|
|
25
|
+
│
|
|
26
|
+
├─ 中小型应用
|
|
27
|
+
│ ├─ 喜欢简洁 → Zustand
|
|
28
|
+
│ └─ 需要原子化 → Jotai
|
|
29
|
+
│
|
|
30
|
+
└─ 大型应用
|
|
31
|
+
├─ 团队熟悉Redux → Redux Toolkit
|
|
32
|
+
├─ 需要时间旅行 → Redux DevTools
|
|
33
|
+
├─ 复杂派生状态 → Recoil
|
|
34
|
+
└─ OOP风格 → MobX
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Redux Toolkit (推荐)
|
|
38
|
+
|
|
39
|
+
### 基础配置
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// store.ts
|
|
43
|
+
import { configureStore } from '@reduxjs/toolkit'
|
|
44
|
+
import counterReducer from './features/counter/counterSlice'
|
|
45
|
+
import userReducer from './features/user/userSlice'
|
|
46
|
+
|
|
47
|
+
export const store = configureStore({
|
|
48
|
+
reducer: {
|
|
49
|
+
counter: counterReducer,
|
|
50
|
+
user: userReducer,
|
|
51
|
+
},
|
|
52
|
+
middleware: (getDefaultMiddleware) =>
|
|
53
|
+
getDefaultMiddleware({
|
|
54
|
+
serializableCheck: {
|
|
55
|
+
ignoredActions: ['user/setTimestamp'],
|
|
56
|
+
},
|
|
57
|
+
}),
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
export type RootState = ReturnType<typeof store.getState>
|
|
61
|
+
export type AppDispatch = typeof store.dispatch
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Slice 定义
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
// counterSlice.ts
|
|
68
|
+
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
|
|
69
|
+
|
|
70
|
+
interface CounterState {
|
|
71
|
+
value: number
|
|
72
|
+
status: 'idle' | 'loading' | 'failed'
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const initialState: CounterState = {
|
|
76
|
+
value: 0,
|
|
77
|
+
status: 'idle',
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export const counterSlice = createSlice({
|
|
81
|
+
name: 'counter',
|
|
82
|
+
initialState,
|
|
83
|
+
reducers: {
|
|
84
|
+
increment: (state) => {
|
|
85
|
+
state.value += 1
|
|
86
|
+
},
|
|
87
|
+
decrement: (state) => {
|
|
88
|
+
state.value -= 1
|
|
89
|
+
},
|
|
90
|
+
incrementByAmount: (state, action: PayloadAction<number>) => {
|
|
91
|
+
state.value += action.payload
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
export const { increment, decrement, incrementByAmount } = counterSlice.actions
|
|
97
|
+
export default counterSlice.reducer
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 异步 Thunk
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// userSlice.ts
|
|
104
|
+
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
|
|
105
|
+
|
|
106
|
+
interface User {
|
|
107
|
+
id: string
|
|
108
|
+
name: string
|
|
109
|
+
email: string
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export const fetchUser = createAsyncThunk(
|
|
113
|
+
'user/fetchUser',
|
|
114
|
+
async (userId: string) => {
|
|
115
|
+
const response = await fetch(`/api/users/${userId}`)
|
|
116
|
+
return (await response.json()) as User
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
const userSlice = createSlice({
|
|
121
|
+
name: 'user',
|
|
122
|
+
initialState: {
|
|
123
|
+
data: null as User | null,
|
|
124
|
+
loading: false,
|
|
125
|
+
error: null as string | null,
|
|
126
|
+
},
|
|
127
|
+
reducers: {},
|
|
128
|
+
extraReducers: (builder) => {
|
|
129
|
+
builder
|
|
130
|
+
.addCase(fetchUser.pending, (state) => {
|
|
131
|
+
state.loading = true
|
|
132
|
+
})
|
|
133
|
+
.addCase(fetchUser.fulfilled, (state, action) => {
|
|
134
|
+
state.loading = false
|
|
135
|
+
state.data = action.payload
|
|
136
|
+
})
|
|
137
|
+
.addCase(fetchUser.rejected, (state, action) => {
|
|
138
|
+
state.loading = false
|
|
139
|
+
state.error = action.error.message || 'Failed'
|
|
140
|
+
})
|
|
141
|
+
},
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
export default userSlice.reducer
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Hooks 使用
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// hooks.ts
|
|
151
|
+
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
|
|
152
|
+
import type { RootState, AppDispatch } from './store'
|
|
153
|
+
|
|
154
|
+
export const useAppDispatch = () => useDispatch<AppDispatch>()
|
|
155
|
+
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
|
156
|
+
|
|
157
|
+
// Component
|
|
158
|
+
import { useAppDispatch, useAppSelector } from './hooks'
|
|
159
|
+
import { increment, fetchUser } from './features/counter/counterSlice'
|
|
160
|
+
|
|
161
|
+
function Counter() {
|
|
162
|
+
const count = useAppSelector((state) => state.counter.value)
|
|
163
|
+
const dispatch = useAppDispatch()
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div>
|
|
167
|
+
<span>{count}</span>
|
|
168
|
+
<button onClick={() => dispatch(increment())}>+</button>
|
|
169
|
+
</div>
|
|
170
|
+
)
|
|
171
|
+
}
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Zustand (轻量推荐)
|
|
175
|
+
|
|
176
|
+
### 基础 Store
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// store.ts
|
|
180
|
+
import { create } from 'zustand'
|
|
181
|
+
|
|
182
|
+
interface BearState {
|
|
183
|
+
bears: number
|
|
184
|
+
increase: () => void
|
|
185
|
+
decrease: () => void
|
|
186
|
+
reset: () => void
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export const useBearStore = create<BearState>((set) => ({
|
|
190
|
+
bears: 0,
|
|
191
|
+
increase: () => set((state) => ({ bears: state.bears + 1 })),
|
|
192
|
+
decrease: () => set((state) => ({ bears: state.bears - 1 })),
|
|
193
|
+
reset: () => set({ bears: 0 }),
|
|
194
|
+
}))
|
|
195
|
+
|
|
196
|
+
// Component
|
|
197
|
+
function BearCounter() {
|
|
198
|
+
const bears = useBearStore((state) => state.bears)
|
|
199
|
+
return <h1>{bears} bears</h1>
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function Controls() {
|
|
203
|
+
const increase = useBearStore((state) => state.increase)
|
|
204
|
+
return <button onClick={increase}>+1</button>
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### 异步 Actions
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
interface UserStore {
|
|
212
|
+
user: User | null
|
|
213
|
+
loading: boolean
|
|
214
|
+
fetchUser: (id: string) => Promise<void>
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export const useUserStore = create<UserStore>((set) => ({
|
|
218
|
+
user: null,
|
|
219
|
+
loading: false,
|
|
220
|
+
fetchUser: async (id) => {
|
|
221
|
+
set({ loading: true })
|
|
222
|
+
try {
|
|
223
|
+
const res = await fetch(`/api/users/${id}`)
|
|
224
|
+
const user = await res.json()
|
|
225
|
+
set({ user, loading: false })
|
|
226
|
+
} catch (error) {
|
|
227
|
+
set({ loading: false })
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
}))
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### 中间件
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
import { create } from 'zustand'
|
|
237
|
+
import { persist, devtools } from 'zustand/middleware'
|
|
238
|
+
|
|
239
|
+
interface AuthState {
|
|
240
|
+
token: string | null
|
|
241
|
+
login: (token: string) => void
|
|
242
|
+
logout: () => void
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export const useAuthStore = create<AuthState>()(
|
|
246
|
+
devtools(
|
|
247
|
+
persist(
|
|
248
|
+
(set) => ({
|
|
249
|
+
token: null,
|
|
250
|
+
login: (token) => set({ token }),
|
|
251
|
+
logout: () => set({ token: null }),
|
|
252
|
+
}),
|
|
253
|
+
{
|
|
254
|
+
name: 'auth-storage',
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
)
|
|
258
|
+
)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Immer 集成
|
|
262
|
+
|
|
263
|
+
```typescript
|
|
264
|
+
import { create } from 'zustand'
|
|
265
|
+
import { immer } from 'zustand/middleware/immer'
|
|
266
|
+
|
|
267
|
+
interface TodoState {
|
|
268
|
+
todos: Array<{ id: string; text: string; done: boolean }>
|
|
269
|
+
addTodo: (text: string) => void
|
|
270
|
+
toggleTodo: (id: string) => void
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export const useTodoStore = create<TodoState>()(
|
|
274
|
+
immer((set) => ({
|
|
275
|
+
todos: [],
|
|
276
|
+
addTodo: (text) =>
|
|
277
|
+
set((state) => {
|
|
278
|
+
state.todos.push({ id: Date.now().toString(), text, done: false })
|
|
279
|
+
}),
|
|
280
|
+
toggleTodo: (id) =>
|
|
281
|
+
set((state) => {
|
|
282
|
+
const todo = state.todos.find((t) => t.id === id)
|
|
283
|
+
if (todo) todo.done = !todo.done
|
|
284
|
+
}),
|
|
285
|
+
}))
|
|
286
|
+
)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
## Jotai (原子化)
|
|
290
|
+
|
|
291
|
+
### Atom 定义
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
import { atom } from 'jotai'
|
|
295
|
+
|
|
296
|
+
// 原始 atom
|
|
297
|
+
export const countAtom = atom(0)
|
|
298
|
+
|
|
299
|
+
// 派生 atom (只读)
|
|
300
|
+
export const doubleCountAtom = atom((get) => get(countAtom) * 2)
|
|
301
|
+
|
|
302
|
+
// 派生 atom (读写)
|
|
303
|
+
export const incrementAtom = atom(
|
|
304
|
+
(get) => get(countAtom),
|
|
305
|
+
(get, set) => set(countAtom, get(countAtom) + 1)
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
// 异步 atom
|
|
309
|
+
export const userAtom = atom(async (get) => {
|
|
310
|
+
const userId = get(userIdAtom)
|
|
311
|
+
const response = await fetch(`/api/users/${userId}`)
|
|
312
|
+
return response.json()
|
|
313
|
+
})
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### 使用 Atoms
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
import { useAtom, useAtomValue, useSetAtom } from 'jotai'
|
|
320
|
+
|
|
321
|
+
function Counter() {
|
|
322
|
+
const [count, setCount] = useAtom(countAtom)
|
|
323
|
+
const doubleCount = useAtomValue(doubleCountAtom)
|
|
324
|
+
const increment = useSetAtom(incrementAtom)
|
|
325
|
+
|
|
326
|
+
return (
|
|
327
|
+
<div>
|
|
328
|
+
<p>Count: {count}</p>
|
|
329
|
+
<p>Double: {doubleCount}</p>
|
|
330
|
+
<button onClick={increment}>+1</button>
|
|
331
|
+
</div>
|
|
332
|
+
)
|
|
333
|
+
}
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### 原子家族
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
import { atomFamily } from 'jotai/utils'
|
|
340
|
+
|
|
341
|
+
// 为每个 ID 创建独立 atom
|
|
342
|
+
export const todoAtomFamily = atomFamily((id: string) =>
|
|
343
|
+
atom({
|
|
344
|
+
id,
|
|
345
|
+
text: '',
|
|
346
|
+
done: false,
|
|
347
|
+
})
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
function TodoItem({ id }: { id: string }) {
|
|
351
|
+
const [todo, setTodo] = useAtom(todoAtomFamily(id))
|
|
352
|
+
|
|
353
|
+
return (
|
|
354
|
+
<div>
|
|
355
|
+
<input
|
|
356
|
+
value={todo.text}
|
|
357
|
+
onChange={(e) => setTodo({ ...todo, text: e.target.value })}
|
|
358
|
+
/>
|
|
359
|
+
<input
|
|
360
|
+
type="checkbox"
|
|
361
|
+
checked={todo.done}
|
|
362
|
+
onChange={(e) => setTodo({ ...todo, done: e.target.checked })}
|
|
363
|
+
/>
|
|
364
|
+
</div>
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### 持久化
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
import { atomWithStorage } from 'jotai/utils'
|
|
373
|
+
|
|
374
|
+
export const themeAtom = atomWithStorage<'light' | 'dark'>('theme', 'light')
|
|
375
|
+
|
|
376
|
+
// 自定义存储
|
|
377
|
+
export const customAtom = atomWithStorage(
|
|
378
|
+
'custom-key',
|
|
379
|
+
{ value: 0 },
|
|
380
|
+
{
|
|
381
|
+
getItem: (key) => {
|
|
382
|
+
const value = localStorage.getItem(key)
|
|
383
|
+
return value ? JSON.parse(value) : { value: 0 }
|
|
384
|
+
},
|
|
385
|
+
setItem: (key, value) => {
|
|
386
|
+
localStorage.setItem(key, JSON.stringify(value))
|
|
387
|
+
},
|
|
388
|
+
removeItem: (key) => {
|
|
389
|
+
localStorage.removeItem(key)
|
|
390
|
+
},
|
|
391
|
+
}
|
|
392
|
+
)
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
## Recoil
|
|
396
|
+
|
|
397
|
+
### Atom 和 Selector
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
import { atom, selector } from 'recoil'
|
|
401
|
+
|
|
402
|
+
// Atom
|
|
403
|
+
export const textState = atom({
|
|
404
|
+
key: 'textState',
|
|
405
|
+
default: '',
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
// Selector (派生状态)
|
|
409
|
+
export const charCountState = selector({
|
|
410
|
+
key: 'charCountState',
|
|
411
|
+
get: ({ get }) => {
|
|
412
|
+
const text = get(textState)
|
|
413
|
+
return text.length
|
|
414
|
+
},
|
|
415
|
+
})
|
|
416
|
+
|
|
417
|
+
// 异步 Selector
|
|
418
|
+
export const userState = selector({
|
|
419
|
+
key: 'userState',
|
|
420
|
+
get: async ({ get }) => {
|
|
421
|
+
const userId = get(userIdState)
|
|
422
|
+
const response = await fetch(`/api/users/${userId}`)
|
|
423
|
+
return response.json()
|
|
424
|
+
},
|
|
425
|
+
})
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
### 使用 Recoil
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
|
432
|
+
|
|
433
|
+
function TextInput() {
|
|
434
|
+
const [text, setText] = useRecoilState(textState)
|
|
435
|
+
const charCount = useRecoilValue(charCountState)
|
|
436
|
+
|
|
437
|
+
return (
|
|
438
|
+
<div>
|
|
439
|
+
<input value={text} onChange={(e) => setText(e.target.value)} />
|
|
440
|
+
<p>Character Count: {charCount}</p>
|
|
441
|
+
</div>
|
|
442
|
+
)
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
### Atom Family
|
|
447
|
+
|
|
448
|
+
```typescript
|
|
449
|
+
import { atomFamily } from 'recoil'
|
|
450
|
+
|
|
451
|
+
export const todoItemState = atomFamily({
|
|
452
|
+
key: 'todoItem',
|
|
453
|
+
default: (id: string) => ({
|
|
454
|
+
id,
|
|
455
|
+
text: '',
|
|
456
|
+
done: false,
|
|
457
|
+
}),
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
function TodoItem({ id }: { id: string }) {
|
|
461
|
+
const [todo, setTodo] = useRecoilState(todoItemState(id))
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<input
|
|
465
|
+
value={todo.text}
|
|
466
|
+
onChange={(e) => setTodo({ ...todo, text: e.target.value })}
|
|
467
|
+
/>
|
|
468
|
+
)
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
## Context API
|
|
473
|
+
|
|
474
|
+
### 基础 Context
|
|
475
|
+
|
|
476
|
+
```typescript
|
|
477
|
+
import { createContext, useContext, useState, ReactNode } from 'react'
|
|
478
|
+
|
|
479
|
+
interface ThemeContextType {
|
|
480
|
+
theme: 'light' | 'dark'
|
|
481
|
+
toggleTheme: () => void
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
|
|
485
|
+
|
|
486
|
+
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
487
|
+
const [theme, setTheme] = useState<'light' | 'dark'>('light')
|
|
488
|
+
|
|
489
|
+
const toggleTheme = () => {
|
|
490
|
+
setTheme((prev) => (prev === 'light' ? 'dark' : 'light'))
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return (
|
|
494
|
+
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
|
495
|
+
{children}
|
|
496
|
+
</ThemeContext.Provider>
|
|
497
|
+
)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export function useTheme() {
|
|
501
|
+
const context = useContext(ThemeContext)
|
|
502
|
+
if (!context) {
|
|
503
|
+
throw new Error('useTheme must be used within ThemeProvider')
|
|
504
|
+
}
|
|
505
|
+
return context
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
### 优化 Context
|
|
510
|
+
|
|
511
|
+
```typescript
|
|
512
|
+
import { createContext, useContext, useMemo, ReactNode } from 'react'
|
|
513
|
+
|
|
514
|
+
// 分离状态和更新函数
|
|
515
|
+
const StateContext = createContext<State | undefined>(undefined)
|
|
516
|
+
const DispatchContext = createContext<Dispatch | undefined>(undefined)
|
|
517
|
+
|
|
518
|
+
export function Provider({ children }: { children: ReactNode }) {
|
|
519
|
+
const [state, dispatch] = useReducer(reducer, initialState)
|
|
520
|
+
|
|
521
|
+
// 防止不必要的重渲染
|
|
522
|
+
const memoizedState = useMemo(() => state, [state])
|
|
523
|
+
const memoizedDispatch = useMemo(() => dispatch, [dispatch])
|
|
524
|
+
|
|
525
|
+
return (
|
|
526
|
+
<StateContext.Provider value={memoizedState}>
|
|
527
|
+
<DispatchContext.Provider value={memoizedDispatch}>
|
|
528
|
+
{children}
|
|
529
|
+
</DispatchContext.Provider>
|
|
530
|
+
</StateContext.Provider>
|
|
531
|
+
)
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
## 性能优化
|
|
536
|
+
|
|
537
|
+
### Redux 选择器优化
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
import { createSelector } from '@reduxjs/toolkit'
|
|
541
|
+
|
|
542
|
+
// 基础选择器
|
|
543
|
+
const selectTodos = (state: RootState) => state.todos
|
|
544
|
+
const selectFilter = (state: RootState) => state.filter
|
|
545
|
+
|
|
546
|
+
// Memoized 选择器
|
|
547
|
+
export const selectFilteredTodos = createSelector(
|
|
548
|
+
[selectTodos, selectFilter],
|
|
549
|
+
(todos, filter) => {
|
|
550
|
+
switch (filter) {
|
|
551
|
+
case 'completed':
|
|
552
|
+
return todos.filter((t) => t.done)
|
|
553
|
+
case 'active':
|
|
554
|
+
return todos.filter((t) => !t.done)
|
|
555
|
+
default:
|
|
556
|
+
return todos
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
)
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Zustand 选择器
|
|
563
|
+
|
|
564
|
+
```typescript
|
|
565
|
+
// 避免不必要的重渲染
|
|
566
|
+
function Component() {
|
|
567
|
+
// ❌ 整个 state 变化都会重渲染
|
|
568
|
+
const state = useStore()
|
|
569
|
+
|
|
570
|
+
// ✅ 只在 bears 变化时重渲染
|
|
571
|
+
const bears = useStore((state) => state.bears)
|
|
572
|
+
|
|
573
|
+
// ✅ 使用 shallow 比较
|
|
574
|
+
const { bears, increase } = useStore(
|
|
575
|
+
(state) => ({ bears: state.bears, increase: state.increase }),
|
|
576
|
+
shallow
|
|
577
|
+
)
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
### Jotai 优化
|
|
582
|
+
|
|
583
|
+
```typescript
|
|
584
|
+
// 使用 selectAtom 避免不必要的重渲染
|
|
585
|
+
import { selectAtom } from 'jotai/utils'
|
|
586
|
+
|
|
587
|
+
const userAtom = atom({ name: 'John', age: 30 })
|
|
588
|
+
const nameAtom = selectAtom(userAtom, (user) => user.name)
|
|
589
|
+
|
|
590
|
+
function Component() {
|
|
591
|
+
// 只在 name 变化时重渲染
|
|
592
|
+
const name = useAtomValue(nameAtom)
|
|
593
|
+
}
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
## 最佳实践
|
|
597
|
+
|
|
598
|
+
### 状态分层
|
|
599
|
+
|
|
600
|
+
```
|
|
601
|
+
全局状态 (Redux/Zustand)
|
|
602
|
+
├─ 用户认证
|
|
603
|
+
├─ 主题配置
|
|
604
|
+
└─ 全局通知
|
|
605
|
+
|
|
606
|
+
服务器状态 (React Query/SWR)
|
|
607
|
+
├─ API 数据
|
|
608
|
+
├─ 缓存管理
|
|
609
|
+
└─ 乐观更新
|
|
610
|
+
|
|
611
|
+
组件状态 (useState/useReducer)
|
|
612
|
+
├─ 表单输入
|
|
613
|
+
├─ UI 交互
|
|
614
|
+
└─ 临时数据
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
### 命名规范
|
|
618
|
+
|
|
619
|
+
```typescript
|
|
620
|
+
// Redux
|
|
621
|
+
const userSlice = createSlice({ name: 'user', ... })
|
|
622
|
+
export const { setUser, clearUser } = userSlice.actions
|
|
623
|
+
|
|
624
|
+
// Zustand
|
|
625
|
+
export const useUserStore = create<UserStore>(...)
|
|
626
|
+
|
|
627
|
+
// Jotai
|
|
628
|
+
export const userAtom = atom<User | null>(null)
|
|
629
|
+
export const userNameAtom = atom((get) => get(userAtom)?.name)
|
|
630
|
+
|
|
631
|
+
// Recoil
|
|
632
|
+
export const userState = atom({ key: 'userState', ... })
|
|
633
|
+
export const userNameState = selector({ key: 'userNameState', ... })
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
### 错误处理
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
// Redux Toolkit
|
|
640
|
+
const userSlice = createSlice({
|
|
641
|
+
name: 'user',
|
|
642
|
+
initialState: {
|
|
643
|
+
data: null,
|
|
644
|
+
error: null as string | null,
|
|
645
|
+
loading: false,
|
|
646
|
+
},
|
|
647
|
+
extraReducers: (builder) => {
|
|
648
|
+
builder.addCase(fetchUser.rejected, (state, action) => {
|
|
649
|
+
state.error = action.error.message || 'Unknown error'
|
|
650
|
+
state.loading = false
|
|
651
|
+
})
|
|
652
|
+
},
|
|
653
|
+
})
|
|
654
|
+
|
|
655
|
+
// Zustand
|
|
656
|
+
export const useStore = create<Store>((set) => ({
|
|
657
|
+
error: null,
|
|
658
|
+
fetchData: async () => {
|
|
659
|
+
try {
|
|
660
|
+
const data = await api.fetch()
|
|
661
|
+
set({ data, error: null })
|
|
662
|
+
} catch (error) {
|
|
663
|
+
set({ error: error.message })
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
}))
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
## 工具清单
|
|
670
|
+
|
|
671
|
+
| 工具 | 用途 |
|
|
672
|
+
|------|------|
|
|
673
|
+
| Redux DevTools | 时间旅行调试 |
|
|
674
|
+
| Zustand DevTools | Zustand 状态调试 |
|
|
675
|
+
| Jotai DevTools | Atom 依赖可视化 |
|
|
676
|
+
| Recoil DevTools | Recoil 状态调试 |
|
|
677
|
+
| React Query DevTools | 服务器状态调试 |
|
|
678
|
+
| Immer | 不可变数据更新 |
|
|
679
|
+
|
|
680
|
+
---
|