@ahoo-wang/fetcher-react 3.5.6 → 3.6.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.
Files changed (82) hide show
  1. package/README.md +869 -483
  2. package/README.zh-CN.md +2139 -605
  3. package/dist/core/debounced/index.d.ts +4 -0
  4. package/dist/core/debounced/index.d.ts.map +1 -0
  5. package/dist/core/debounced/useDebouncedCallback.d.ts.map +1 -0
  6. package/dist/core/{useDebouncedExecutePromise.d.ts → debounced/useDebouncedExecutePromise.d.ts} +1 -1
  7. package/dist/core/debounced/useDebouncedExecutePromise.d.ts.map +1 -0
  8. package/dist/{wow/debounce → core/debounced}/useDebouncedQuery.d.ts +1 -2
  9. package/dist/core/debounced/useDebouncedQuery.d.ts.map +1 -0
  10. package/dist/core/index.d.ts +3 -2
  11. package/dist/core/index.d.ts.map +1 -1
  12. package/dist/{wow → core}/useQuery.d.ts +2 -2
  13. package/dist/core/useQuery.d.ts.map +1 -0
  14. package/dist/{wow → core}/useQueryState.d.ts +1 -1
  15. package/dist/core/useQueryState.d.ts.map +1 -0
  16. package/dist/cosec/RouteGuard.d.ts +49 -0
  17. package/dist/cosec/RouteGuard.d.ts.map +1 -0
  18. package/dist/cosec/SecurityContext.d.ts +47 -14
  19. package/dist/cosec/SecurityContext.d.ts.map +1 -1
  20. package/dist/cosec/index.d.ts +1 -0
  21. package/dist/cosec/index.d.ts.map +1 -1
  22. package/dist/cosec/useSecurity.d.ts +60 -8
  23. package/dist/cosec/useSecurity.d.ts.map +1 -1
  24. package/dist/fetcher/debounced/index.d.ts +3 -0
  25. package/dist/fetcher/debounced/index.d.ts.map +1 -0
  26. package/dist/fetcher/{useDebouncedFetcher.d.ts → debounced/useDebouncedFetcher.d.ts} +2 -2
  27. package/dist/fetcher/debounced/useDebouncedFetcher.d.ts.map +1 -0
  28. package/dist/{wow/debounce → fetcher/debounced}/useDebouncedFetcherQuery.d.ts +1 -1
  29. package/dist/fetcher/debounced/useDebouncedFetcherQuery.d.ts.map +1 -0
  30. package/dist/fetcher/index.d.ts +2 -1
  31. package/dist/fetcher/index.d.ts.map +1 -1
  32. package/dist/{wow → fetcher}/useFetcherQuery.d.ts +3 -3
  33. package/dist/fetcher/useFetcherQuery.d.ts.map +1 -0
  34. package/dist/index.es.js +567 -538
  35. package/dist/index.es.js.map +1 -1
  36. package/dist/index.umd.js +1 -1
  37. package/dist/index.umd.js.map +1 -1
  38. package/dist/types.d.ts +6 -0
  39. package/dist/types.d.ts.map +1 -1
  40. package/dist/wow/fetcher/index.d.ts +6 -0
  41. package/dist/wow/fetcher/index.d.ts.map +1 -0
  42. package/dist/wow/{useFetcherCountQuery.d.ts → fetcher/useFetcherCountQuery.d.ts} +2 -2
  43. package/dist/wow/fetcher/useFetcherCountQuery.d.ts.map +1 -0
  44. package/dist/wow/{useFetcherListQuery.d.ts → fetcher/useFetcherListQuery.d.ts} +2 -2
  45. package/dist/wow/fetcher/useFetcherListQuery.d.ts.map +1 -0
  46. package/dist/wow/{useFetcherListStreamQuery.d.ts → fetcher/useFetcherListStreamQuery.d.ts} +2 -2
  47. package/dist/wow/fetcher/useFetcherListStreamQuery.d.ts.map +1 -0
  48. package/dist/wow/{useFetcherPagedQuery.d.ts → fetcher/useFetcherPagedQuery.d.ts} +2 -2
  49. package/dist/wow/fetcher/useFetcherPagedQuery.d.ts.map +1 -0
  50. package/dist/wow/{useFetcherSingleQuery.d.ts → fetcher/useFetcherSingleQuery.d.ts} +2 -2
  51. package/dist/wow/fetcher/useFetcherSingleQuery.d.ts.map +1 -0
  52. package/dist/wow/index.d.ts +1 -10
  53. package/dist/wow/index.d.ts.map +1 -1
  54. package/dist/wow/useCountQuery.d.ts +1 -1
  55. package/dist/wow/useCountQuery.d.ts.map +1 -1
  56. package/dist/wow/useListQuery.d.ts +1 -1
  57. package/dist/wow/useListQuery.d.ts.map +1 -1
  58. package/dist/wow/useListStreamQuery.d.ts +1 -1
  59. package/dist/wow/useListStreamQuery.d.ts.map +1 -1
  60. package/dist/wow/usePagedQuery.d.ts +1 -1
  61. package/dist/wow/usePagedQuery.d.ts.map +1 -1
  62. package/dist/wow/useSingleQuery.d.ts +1 -1
  63. package/dist/wow/useSingleQuery.d.ts.map +1 -1
  64. package/package.json +1 -1
  65. package/dist/core/useDebouncedCallback.d.ts.map +0 -1
  66. package/dist/core/useDebouncedExecutePromise.d.ts.map +0 -1
  67. package/dist/fetcher/useDebouncedFetcher.d.ts.map +0 -1
  68. package/dist/wow/debounce/index.d.ts +0 -3
  69. package/dist/wow/debounce/index.d.ts.map +0 -1
  70. package/dist/wow/debounce/useDebouncedFetcherQuery.d.ts.map +0 -1
  71. package/dist/wow/debounce/useDebouncedQuery.d.ts.map +0 -1
  72. package/dist/wow/types.d.ts +0 -7
  73. package/dist/wow/types.d.ts.map +0 -1
  74. package/dist/wow/useFetcherCountQuery.d.ts.map +0 -1
  75. package/dist/wow/useFetcherListQuery.d.ts.map +0 -1
  76. package/dist/wow/useFetcherListStreamQuery.d.ts.map +0 -1
  77. package/dist/wow/useFetcherPagedQuery.d.ts.map +0 -1
  78. package/dist/wow/useFetcherQuery.d.ts.map +0 -1
  79. package/dist/wow/useFetcherSingleQuery.d.ts.map +0 -1
  80. package/dist/wow/useQuery.d.ts.map +0 -1
  81. package/dist/wow/useQueryState.d.ts.map +0 -1
  82. /package/dist/core/{useDebouncedCallback.d.ts → debounced/useDebouncedCallback.d.ts} +0 -0
package/README.zh-CN.md CHANGED
@@ -27,37 +27,47 @@
27
27
  - [快速开始](#快速开始)
28
28
  - [使用方法](#使用方法)
29
29
  - [核心 Hooks](#核心-hooks)
30
- - [useFetcher](#usefetcher-hook)
31
30
  - [useExecutePromise](#useexecutepromise)
32
31
  - [usePromiseState](#usepromisestate)
33
- - [防抖 Hooks](#防抖-hooks)
34
- - [useDebouncedCallback](#usedebouncedcallback)
35
- - [useDebouncedExecutePromise](#usedebouncedexecutepromise)
36
- - [useDebouncedFetcher](#usedebouncedfetcher)
37
- - [useDebouncedFetcherQuery](#usedebouncedfetcherquery)
38
- - [useDebouncedQuery](#usedebouncedquery)
39
- - [工具 Hooks](#工具-hooks)
40
- - [useRequestId](#userequestid)
41
- - [useLatest](#uselatest)
42
- - [useRefs](#userefs)
32
+ - [useRequestId](#userequestid-hook)
33
+ - [useLatest](#uselatest-hook)
34
+ - [useRefs](#userefs-hook)
35
+ - [useQuery](#usequery-hook)
36
+ - [useQueryState](#usequerystate-hook)
37
+ - [useMounted](#usemounted-hook)
38
+ - [useForceUpdate](#useforceupdate-hook)
39
+ - [防抖 Hooks](#防抖-hooks)
40
+ - [useDebouncedCallback](#usedebouncedcallback)
41
+ - [useDebouncedExecutePromise](#usedebouncedexecutepromise)
42
+ - [useDebouncedQuery](#usedebouncedquery)
43
+ - [Fetcher Hooks](#fetcher-hooks)
44
+ - [useFetcher](#usefetcher-hook)
45
+ - [useFetcherQuery](#usefetcherquery-hook)
46
+ - [防抖 Fetcher Hooks](#防抖-fetcher-hooks)
47
+ - [useDebouncedFetcher](#usedebouncedfetcher)
48
+ - [useDebouncedFetcherQuery](#usedebouncedfetcherquery)
43
49
  - [存储 Hooks](#存储-hooks)
44
- - [useKeyStorage](#usekeystorage)
50
+ - [useKeyStorage](#usekeystorage-hook)
45
51
  - [useImmerKeyStorage](#useimmerkeystorage-hook)
46
52
  - [事件 Hooks](#事件-hooks)
47
53
  - [useEventSubscription](#useeventsubscription-hook)
54
+ - [CoSec 安全 Hooks](#cosec-安全-hooks)
55
+ - [useSecurity](#usesecurity-hook)
56
+ - [SecurityProvider](#securityprovider)
57
+ - [useSecurityContext](#usesecuritycontext-hook)
58
+ - [RouteGuard](#routeguard)
48
59
  - [Wow 查询 Hooks](#wow-查询-hooks)
49
60
  - [基础查询 Hooks](#基础查询-hooks)
50
61
  - [useListQuery](#uselistquery-hook)
51
62
  - [usePagedQuery](#usepagedquery-hook)
52
63
  - [useSingleQuery](#usesinglequery-hook)
53
64
  - [useCountQuery](#usecountquery-hook)
54
- - [获取查询 Hooks](#获取查询-hooks)
65
+ - [useListStreamQuery](#useliststreamquery-hook)
66
+ - [Fetcher 查询 Hooks](#fetcher-查询-hooks)
55
67
  - [useFetcherListQuery](#usefetcherlistquery-hook)
56
68
  - [useFetcherPagedQuery](#usefetcherpagedquery-hook)
57
69
  - [useFetcherSingleQuery](#usefetchersinglequery-hook)
58
70
  - [useFetcherCountQuery](#usefetchercountquery-hook)
59
- - [流查询 Hooks](#流查询-hooks)
60
- - [useListStreamQuery](#useliststreamquery-hook)
61
71
  - [useFetcherListStreamQuery](#usefetcherliststreamquery-hook)
62
72
  - [最佳实践](#最佳实践)
63
73
  - [API 参考](#api-参考)
@@ -101,286 +111,1241 @@ function App() {
101
111
 
102
112
  ### 核心 Hooks
103
113
 
104
- #### useFetcher Hook
114
+ #### useExecutePromise
105
115
 
106
- `useFetcher` hook 提供完整的数据获取功能,具有自动状态管理、竞态条件保护和灵活的配置选项。
116
+ `useExecutePromise` hook 用于管理异步操作,具有自动状态处理、竞态条件保护和 Promise 状态选项。它包含用于取消操作的自动 AbortController 支持。
107
117
 
108
118
  ```typescript jsx
109
- import { useFetcher } from '@ahoo-wang/fetcher-react';
119
+ import { useExecutePromise } from '@ahoo-wang/fetcher-react';
110
120
 
111
121
  const MyComponent = () => {
112
- const { loading, error, result, execute } = useFetcher<string>();
122
+ const { loading, result, error, execute, reset, abort } = useExecutePromise<string>({
123
+ onAbort: () => {
124
+ console.log('操作已被中止');
125
+ }
126
+ });
127
+
128
+ const fetchData = async () => {
129
+ const response = await fetch('/api/data');
130
+ return response.text();
131
+ };
113
132
 
114
133
  const handleFetch = () => {
115
- execute({ url: '/api/users', method: 'GET' });
116
- };
117
- ```
134
+ execute(fetchData); // 使用 Promise 供应商
135
+ };
118
136
 
119
- ### useImmerKeyStorage Hook
137
+ const handleDirectPromise = () => {
138
+ const promise = fetch('/api/data').then(res => res.text());
139
+ execute(promise); // 使用直接 Promise
140
+ };
120
141
 
121
- 🚀 **Immer 驱动的不可变状态管理** - `useImmerKeyStorage` hook 通过集成 Immer 的 `produce` 函数扩展了 `useKeyStorage`,允许开发者以直观的"可变"方式更新存储值,同时在底层保持不可变性。非常适合复杂对象的操作,具有自动存储同步功能。
142
+ const handleAbort = () => {
143
+ abort(); // 手动中止当前操作
144
+ };
122
145
 
123
- #### 主要优势
146
+ if (loading) return <div>加载中...</div>;
147
+ if (error) return <div>错误: {error.message}</div>;
148
+ return (
149
+ <div>
150
+ <button onClick={handleFetch}>使用供应商获取</button>
151
+ <button onClick={handleDirectPromise}>使用 Promise 获取</button>
152
+ <button onClick={handleAbort} disabled={!loading}>中止</button>
153
+ <button onClick={reset}>重置</button>
154
+ {result && <p>{result}</p>}
155
+ </div>
156
+ );
157
+ };
158
+ ```
124
159
 
125
- - **直观的变更语法**: 编写看起来可变的代码,但产生不可变更新
126
- - **深度对象支持**: 轻松处理嵌套对象和数组
127
- - **类型安全**: 完整的 TypeScript 支持和编译时错误检查
128
- - **性能优化**: 利用 Immer 的结构共享和最小化重渲染
129
- - **自动同步**: 变更自动持久化到存储并跨组件同步
160
+ ##### 中止控制器支持
130
161
 
131
- #### 使用场景
162
+ hook 会为每个操作自动创建一个 AbortController,并提供管理取消的方法:
132
163
 
133
- 在需要以下情况时选择 `useImmerKeyStorage` 而不是 `useKeyStorage`:
164
+ - **自动清理**: 组件卸载时操作会自动中止
165
+ - **手动中止**: 使用 `abort()` 方法中止正在进行的操作
166
+ - **onAbort 回调**: 配置在操作中止时触发的回调(手动或自动)
167
+ - **AbortController 访问**: AbortController 会传递给 Promise 供应商以进行高级取消处理
134
168
 
135
- - 更新嵌套对象属性
136
- - 执行复杂的数组操作(push、splice 等)
137
- - 原子性地进行多个相关变更
138
- - 处理深度嵌套的数据结构
169
+ #### usePromiseState
170
+
171
+ `usePromiseState` hook 提供 Promise 操作的状态管理,无执行逻辑。支持静态选项和动态选项供应商。
139
172
 
140
173
  ```typescript jsx
141
- import { KeyStorage } from '@ahoo-wang/fetcher-storage';
142
- import { useImmerKeyStorage } from '@ahoo-wang/fetcher-react';
174
+ import { usePromiseState, PromiseStatus } from '@ahoo-wang/fetcher-react';
143
175
 
144
176
  const MyComponent = () => {
145
- const prefsStorage = new KeyStorage<{
146
- theme: string;
147
- volume: number;
148
- notifications: boolean;
149
- shortcuts: { [key: string]: string };
150
- }>({
151
- key: 'user-prefs'
152
- });
177
+ const { status, loading, result, error, setSuccess, setError, setIdle } = usePromiseState<string>();
153
178
 
154
- // 不使用默认值 - 可能为 null
155
- const [prefs, updatePrefs, clearPrefs] = useImmerKeyStorage(prefsStorage);
179
+ const handleSuccess = () => setSuccess('数据已加载');
180
+ const handleError = () => setError(new Error('加载失败'));
156
181
 
157
182
  return (
158
183
  <div>
159
- <p>主题: {prefs?.theme || '默认'}</p>
160
- <button onClick={() => updatePrefs(draft => { draft.theme = 'dark'; })}>
161
- 切换到深色主题
162
- </button>
163
- <button onClick={() => updatePrefs(draft => { draft.volume += 10; })}>
164
- 增加音量
165
- </button>
166
- <button onClick={clearPrefs}>
167
- 清除偏好设置
168
- </button>
184
+ <button onClick={handleSuccess}>设置成功</button>
185
+ <button onClick={handleError}>设置错误</button>
186
+ <button onClick={setIdle}>重置</button>
187
+ <p>状态: {status}</p>
188
+ {loading && <p>加载中...</p>}
189
+ {result && <p>结果: {result}</p>}
190
+ {error && <p>错误: {error.message}</p>}
169
191
  </div>
170
192
  );
171
193
  };
172
194
  ```
173
195
 
174
- #### 使用默认值
196
+ ##### 使用选项供应商的 usePromiseState
175
197
 
176
198
  ```typescript jsx
177
- const AudioControls = () => {
178
- const settingsStorage = new KeyStorage<{ volume: number; muted: boolean }>({
179
- key: 'audio-settings'
199
+ import { usePromiseState, PromiseStatus } from '@ahoo-wang/fetcher-react';
200
+
201
+ const MyComponent = () => {
202
+ // 使用选项供应商进行动态配置
203
+ const optionsSupplier = () => ({
204
+ initialStatus: PromiseStatus.IDLE,
205
+ onSuccess: async (result: string) => {
206
+ await saveToAnalytics(result);
207
+ console.log('成功:', result);
208
+ },
209
+ onError: async (error) => {
210
+ await logErrorToServer(error);
211
+ console.error('错误:', error);
212
+ },
180
213
  });
181
214
 
182
- // 使用默认值 - 保证不为 null
183
- const [settings, updateSettings, resetSettings] = useImmerKeyStorage(
184
- settingsStorage,
185
- { volume: 50, muted: false }
186
- );
215
+ const { setSuccess, setError } = usePromiseState<string>(optionsSupplier);
187
216
 
188
217
  return (
189
218
  <div>
190
- <p>音量: {settings.volume}%</p>
191
- <button onClick={() => updateSettings(draft => {
192
- draft.volume = Math.min(100, draft.volume + 10);
193
- draft.muted = false;
194
- })}>
195
- 增加音量
196
- </button>
197
- <button onClick={() => updateSettings(draft => { draft.muted = !draft.muted; })}>
198
- 切换静音
199
- </button>
200
- <button onClick={resetSettings}>
201
- 重置为默认值
202
- </button>
219
+ <button onClick={() => setSuccess('动态成功!')}>设置成功</button>
220
+ <button onClick={() => setError(new Error('动态错误!'))}>设置错误</button>
203
221
  </div>
204
222
  );
205
223
  };
206
224
  ```
207
225
 
208
- #### 高级用法模式
226
+ #### useRequestId
209
227
 
210
- ##### 批量更新
228
+ `useRequestId` hook 提供请求 ID 管理,用于防止异步操作中的竞态条件。
211
229
 
212
230
  ```typescript jsx
213
- const updateUserProfile = () => {
214
- updatePrefs(draft => {
215
- draft.theme = 'dark';
216
- draft.notifications = true;
217
- draft.volume = 75;
218
- });
219
- };
220
- ```
221
-
222
- ##### 数组操作
231
+ import { useRequestId } from '@ahoo-wang/fetcher-react';
223
232
 
224
- ```typescript jsx
225
- const todoStorage = new KeyStorage<{
226
- todos: Array<{ id: number; text: string; done: boolean }>;
227
- }>({
228
- key: 'todos',
229
- });
233
+ const MyComponent = () => {
234
+ const { generate, isLatest, invalidate } = useRequestId();
230
235
 
231
- const [state, updateState] = useImmerKeyStorage(todoStorage, { todos: [] });
236
+ const handleFetch = async () => {
237
+ const requestId = generate();
232
238
 
233
- // 添加新待办事项
234
- const addTodo = (text: string) => {
235
- updateState(draft => {
236
- draft.todos.push({
237
- id: Date.now(),
238
- text,
239
- done: false,
240
- });
241
- });
242
- };
239
+ try {
240
+ const result = await fetchData();
243
241
 
244
- // 切换待办事项状态
245
- const toggleTodo = (id: number) => {
246
- updateState(draft => {
247
- const todo = draft.todos.find(t => t.id === id);
248
- if (todo) {
249
- todo.done = !todo.done;
242
+ if (isLatest(requestId)) {
243
+ setData(result);
244
+ }
245
+ } catch (error) {
246
+ if (isLatest(requestId)) {
247
+ setError(error);
248
+ }
250
249
  }
251
- });
252
- };
250
+ };
253
251
 
254
- // 清除已完成的待办事项
255
- const clearCompleted = () => {
256
- updateState(draft => {
257
- draft.todos = draft.todos.filter(todo => !todo.done);
258
- });
252
+ return (
253
+ <div>
254
+ <button onClick={handleFetch}>获取数据</button>
255
+ <button onClick={invalidate}>取消进行中</button>
256
+ </div>
257
+ );
259
258
  };
260
259
  ```
261
260
 
262
- ##### 嵌套对象更新
261
+ #### useLatest
262
+
263
+ `useLatest` hook 返回包含最新值的 ref 对象,用于在异步回调中访问当前值。
263
264
 
264
265
  ```typescript jsx
265
- const configStorage = new KeyStorage<{
266
- ui: { theme: string; language: string };
267
- features: { [key: string]: boolean };
268
- }>({
269
- key: 'app-config',
270
- });
266
+ import { useLatest } from '@ahoo-wang/fetcher-react';
271
267
 
272
- const [config, updateConfig] = useImmerKeyStorage(configStorage, {
273
- ui: { theme: 'light', language: 'zh' },
274
- features: {},
275
- });
268
+ const MyComponent = () => {
269
+ const [count, setCount] = useState(0);
270
+ const latestCount = useLatest(count);
276
271
 
277
- // 更新嵌套属性
278
- const updateTheme = (theme: string) => {
279
- updateConfig(draft => {
280
- draft.ui.theme = theme;
281
- });
282
- };
272
+ const handleAsync = async () => {
273
+ await someAsyncOperation();
274
+ console.log('最新计数:', latestCount.current); // 始终是最新的
275
+ };
283
276
 
284
- const toggleFeature = (feature: string) => {
285
- updateConfig(draft => {
286
- draft.features[feature] = !draft.features[feature];
287
- });
277
+ return (
278
+ <div>
279
+ <p>计数: {count}</p>
280
+ <button onClick={() => setCount(c => c + 1)}>增加</button>
281
+ <button onClick={handleAsync}>异步日志</button>
282
+ </div>
283
+ );
288
284
  };
289
285
  ```
290
286
 
291
- ##### 带验证的条件更新
292
-
293
- ```typescript jsx
294
- const updateVolume = (newVolume: number) => {
295
- updateSettings(draft => {
296
- if (newVolume >= 0 && newVolume <= 100) {
297
- draft.volume = newVolume;
298
- draft.muted = false; // 音量改变时取消静音
299
- }
300
- });
301
- };
302
- ```
287
+ #### useRefs
303
288
 
304
- ##### 返回新值
289
+ `useRefs` hook 提供 Map-like 接口用于动态管理多个 React refs。它允许通过键注册、检索和管理 refs,并在组件卸载时自动清理。
305
290
 
306
291
  ```typescript jsx
307
- // 替换整个状态
308
- const resetToFactorySettings = () => {
309
- updateSettings(() => ({ volume: 50, muted: false }));
310
- };
292
+ import { useRefs } from '@ahoo-wang/fetcher-react';
311
293
 
312
- // 计算更新
313
- const setMaxVolume = () => {
314
- updateSettings(draft => ({ ...draft, volume: 100, muted: false }));
315
- };
316
- ```
294
+ const MyComponent = () => {
295
+ const refs = useRefs<HTMLDivElement>();
317
296
 
318
- ##### 错误处理
297
+ const handleFocus = (key: string) => {
298
+ const element = refs.get(key);
299
+ element?.focus();
300
+ };
319
301
 
320
- ```typescript jsx
321
- const safeUpdate = (updater: (draft: any) => void) => {
322
- try {
323
- updatePrefs(updater);
324
- } catch (error) {
325
- console.error('更新偏好设置失败:', error);
326
- // 适当处理错误
327
- }
302
+ return (
303
+ <div>
304
+ <div ref={refs.register('first')} tabIndex={0}>第一个元素</div>
305
+ <div ref={refs.register('second')} tabIndex={0}>第二个元素</div>
306
+ <button onClick={() => handleFocus('first')}>聚焦第一个</button>
307
+ <button onClick={() => handleFocus('second')}>聚焦第二个</button>
308
+ </div>
309
+ );
328
310
  };
329
311
  ```
330
312
 
331
- #### 最佳实践
313
+ 主要特性:
332
314
 
333
- ##### 推荐做法
315
+ - **动态注册**: 使用字符串、数字或符号键注册 refs
316
+ - **Map-like API**: 完整的 Map 接口,包括 get、set、has、delete 等
317
+ - **自动清理**: 组件卸载时清除 refs
318
+ - **类型安全**: 完整的 TypeScript 支持 ref 类型
334
319
 
335
- - 用于复杂对象更新和数组操作
336
- - 利用 Immer 的 draft 变更编写可读代码
337
- - 在单个更新调用中组合多个相关变更
338
- - 对保证非空状态使用默认值
339
- - 在更新函数中适当处理错误
320
+ #### useQuery
340
321
 
341
- ##### 避免做法
322
+ `useQuery` hook 提供完整的查询基础异步操作管理解决方案,具有自动状态管理和执行控制。
342
323
 
343
- - 不要直接用赋值修改 draft 参数(`draft = newValue`)
344
- - 不要在更新函数中执行副作用
345
- - 不要依赖对象比较的引用相等性
346
- - 不要用于简单的原始值更新(应使用 `useKeyStorage`)
324
+ ```typescript jsx
325
+ import { useQuery } from '@ahoo-wang/fetcher-react';
347
326
 
348
- ##### 性能提示
327
+ interface UserQuery {
328
+ id: string;
329
+ }
349
330
 
350
- - 将相关更新批量处理以最小化存储操作
351
- - 当新状态依赖于之前状态时使用函数式更新
352
- - 如果更新函数经常重新创建,考虑使用 `useCallback`
353
- - 如果处理非常大的对象,请分析更新性能
331
+ interface User {
332
+ id: string;
333
+ name: string;
334
+ }
354
335
 
355
- ##### TypeScript 集成
336
+ function UserComponent() {
337
+ const { loading, result, error, execute, setQuery } = useQuery<UserQuery, User>({
338
+ initialQuery: { id: '1' },
339
+ execute: async (query) => {
340
+ const response = await fetch(`/api/users/${query.id}`);
341
+ return response.json();
342
+ },
343
+ autoExecute: true,
344
+ });
356
345
 
357
- ```typescript jsx
358
- // 为更好的安全性定义严格类型
359
- type UserPreferences = {
360
- theme: 'light' | 'dark' | 'auto';
361
- volume: number; // 0-100
362
- notifications: boolean;
363
- shortcuts: Record<string, string>;
364
- };
346
+ const handleUserChange = (userId: string) => {
347
+ setQuery({ id: userId }); // 如果 autoExecute 为 true 会自动执行
348
+ };
365
349
 
366
- const prefsStorage = new KeyStorage<UserPreferences>({
367
- key: 'user-prefs',
368
- });
350
+ if (loading) return <div>加载中...</div>;
351
+ if (error) return <div>错误: {error.message}</div>;
352
+ return (
353
+ <div>
354
+ <button onClick={() => handleUserChange('2')}>加载用户 2</button>
355
+ {result && <p>用户: {result.name}</p>}
356
+ </div>
357
+ );
358
+ }
359
+ ```
369
360
 
370
- // TypeScript 将捕获无效更新
371
- const [prefs, updatePrefs] = useImmerKeyStorage(prefsStorage);
361
+ #### useQueryState
362
+
363
+ `useQueryState` hook 提供查询参数的状态管理,具有自动执行功能。
364
+
365
+ ```typescript jsx
366
+ import { useQueryState } from '@ahoo-wang/fetcher-react';
367
+
368
+ interface UserQuery {
369
+ id: string;
370
+ name?: string;
371
+ }
372
+
373
+ function UserComponent() {
374
+ const executeQuery = async (query: UserQuery) => {
375
+ // 执行查询逻辑
376
+ console.log('执行查询:', query);
377
+ };
378
+
379
+ const { getQuery, setQuery } = useQueryState<UserQuery>({
380
+ initialQuery: { id: '1' },
381
+ autoExecute: true,
382
+ execute: executeQuery,
383
+ });
384
+
385
+ const handleQueryChange = (newQuery: UserQuery) => {
386
+ setQuery(newQuery); // 如果 autoExecute 为 true 会自动执行
387
+ };
388
+
389
+ const currentQuery = getQuery(); // 获取当前查询参数
390
+
391
+ return (
392
+ <div>
393
+ <button onClick={() => handleQueryChange({ id: '2', name: 'John' })}>
394
+ 更新查询
395
+ </button>
396
+ </div>
397
+ );
398
+ }
399
+ ```
400
+
401
+ #### useMounted
402
+
403
+ `useMounted` hook 提供检查组件是否仍挂载的方法,用于避免在卸载组件上更新状态。
404
+
405
+ ```typescript jsx
406
+ import { useMounted } from '@ahoo-wang/fetcher-react';
407
+
408
+ const MyComponent = () => {
409
+ const isMounted = useMounted();
410
+
411
+ const handleAsyncOperation = async () => {
412
+ const result = await someAsyncOperation();
413
+
414
+ // 检查组件是否仍挂载后再更新状态
415
+ if (isMounted()) {
416
+ setData(result);
417
+ }
418
+ };
419
+
420
+ return (
421
+ <div>
422
+ <button onClick={handleAsyncOperation}>执行异步操作</button>
423
+ </div>
424
+ );
425
+ };
426
+ ```
427
+
428
+ #### useForceUpdate
429
+
430
+ `useForceUpdate` hook 提供强制组件重新渲染的方法,用于在需要基于外部变化触发渲染时使用。
431
+
432
+ ```typescript jsx
433
+ import { useForceUpdate } from '@ahoo-wang/fetcher-react';
434
+
435
+ const MyComponent = () => {
436
+ const forceUpdate = useForceUpdate();
437
+
438
+ const handleExternalChange = () => {
439
+ // 执行不会触发重新渲染的外部操作
440
+ updateExternalState();
441
+
442
+ // 强制组件重新渲染以反映变化
443
+ forceUpdate();
444
+ };
445
+
446
+ return (
447
+ <div>
448
+ <button onClick={handleExternalChange}>强制更新</button>
449
+ </div>
450
+ );
451
+ };
452
+ ```
453
+
454
+ ### 防抖 Hooks
455
+
456
+ 🚀 **React 应用的高级防抖** - 强大的 hooks,将防抖与异步操作结合,为 API 调用、用户交互和 Promise 执行提供无缝的速率限制。
457
+
458
+ #### useDebouncedCallback
459
+
460
+ React hook,为任何回调函数提供防抖版本,支持前缘/后缘执行选项。
461
+
462
+ ```typescript jsx
463
+ import { useDebouncedCallback } from '@ahoo-wang/fetcher-react';
464
+
465
+ const SearchComponent = () => {
466
+ const { run: debouncedSearch, cancel, isPending } = useDebouncedCallback(
467
+ async (query: string) => {
468
+ const response = await fetch(`/api/search?q=${query}`);
469
+ const results = await response.json();
470
+ console.log('搜索结果:', results);
471
+ },
472
+ { delay: 300 }
473
+ );
474
+
475
+ const handleSearch = (query: string) => {
476
+ if (query.trim()) {
477
+ debouncedSearch(query);
478
+ } else {
479
+ cancel(); // 取消任何待处理的搜索
480
+ }
481
+ };
482
+
483
+ return (
484
+ <div>
485
+ <input
486
+ type="text"
487
+ placeholder="搜索..."
488
+ onChange={(e) => handleSearch(e.target.value)}
489
+ />
490
+ {isPending() && <div>搜索中...</div>}
491
+ </div>
492
+ );
493
+ };
494
+ ```
495
+
496
+ **配置选项:**
497
+
498
+ - `delay`: 执行前的延迟毫秒数(必需,正数)
499
+ - `leading`: 第一次调用时立即执行(默认:false)
500
+ - `trailing`: 最后一次调用后延迟执行(默认:true)
501
+
502
+ #### useDebouncedExecutePromise
503
+
504
+ 将 Promise 执行与防抖功能结合,适用于 API 调用和异步操作。
505
+
506
+ ```typescript jsx
507
+ import { useDebouncedExecutePromise } from '@ahoo-wang/fetcher-react';
508
+
509
+ const DataFetcher = () => {
510
+ const { loading, result, error, run } = useDebouncedExecutePromise({
511
+ debounce: { delay: 300 },
512
+ });
513
+
514
+ const handleLoadUser = (userId: string) => {
515
+ run(async () => {
516
+ const response = await fetch(`/api/users/${userId}`);
517
+ return response.json();
518
+ });
519
+ };
520
+
521
+ return (
522
+ <div>
523
+ <button onClick={() => handleLoadUser('user123')}>
524
+ 加载用户
525
+ </button>
526
+ {loading && <div>加载中...</div>}
527
+ {error && <div>错误: {error.message}</div>}
528
+ {result && <div>用户: {result.name}</div>}
529
+ </div>
530
+ );
531
+ };
532
+ ```
533
+
534
+ #### useDebouncedQuery
535
+
536
+ 将通用查询执行与防抖结合,适用于自定义查询操作,您希望根据查询参数防抖执行。
537
+
538
+ ```typescript jsx
539
+ import { useDebouncedQuery } from '@ahoo-wang/fetcher-react';
540
+
541
+ interface SearchQuery {
542
+ keyword: string;
543
+ limit: number;
544
+ filters?: { category?: string };
545
+ }
546
+
547
+ interface SearchResult {
548
+ items: Array<{ id: string; title: string }>;
549
+ total: number;
550
+ }
551
+
552
+ const SearchComponent = () => {
553
+ const {
554
+ loading,
555
+ result,
556
+ error,
557
+ run,
558
+ cancel,
559
+ isPending,
560
+ setQuery,
561
+ getQuery,
562
+ } = useDebouncedQuery<SearchQuery, SearchResult>({
563
+ initialQuery: { keyword: '', limit: 10 },
564
+ execute: async (query) => {
565
+ const response = await fetch('/api/search', {
566
+ method: 'POST',
567
+ body: JSON.stringify(query),
568
+ headers: { 'Content-Type': 'application/json' },
569
+ });
570
+ return response.json();
571
+ },
572
+ debounce: { delay: 300 }, // 防抖 300ms
573
+ autoExecute: false, // 挂载时不执行
574
+ });
575
+
576
+ const handleSearch = (keyword: string) => {
577
+ setQuery({ keyword, limit: 10 }); // 如果 autoExecute 为 true,这将触发防抖执行
578
+ };
579
+
580
+ const handleManualSearch = () => {
581
+ run(); // 使用当前查询手动防抖执行
582
+ };
583
+
584
+ const handleCancel = () => {
585
+ cancel(); // 取消任何待处理的防抖执行
586
+ };
587
+
588
+ if (loading) return <div>搜索中...</div>;
589
+ if (error) return <div>错误: {error.message}</div>;
590
+
591
+ return (
592
+ <div>
593
+ <input
594
+ type="text"
595
+ onChange={(e) => handleSearch(e.target.value)}
596
+ placeholder="搜索..."
597
+ />
598
+ <button onClick={handleManualSearch} disabled={isPending()}>
599
+ {isPending() ? '搜索中...' : '搜索'}
600
+ </button>
601
+ <button onClick={handleCancel}>取消</button>
602
+ {result && (
603
+ <div>
604
+ 找到 {result.total} 项:
605
+ {result.items.map(item => (
606
+ <div key={item.id}>{item.title}</div>
607
+ ))}
608
+ </div>
609
+ )}
610
+ </div>
611
+ );
612
+ };
613
+ ```
614
+
615
+ **主要特性:**
616
+
617
+ - **查询状态管理**: 使用 `setQuery` 和 `getQuery` 自动查询参数处理
618
+ - **防抖执行**: 在快速查询更改期间防止过多操作
619
+ - **自动执行**: 可选的在查询参数更改时自动执行
620
+ - **手动控制**: `run()` 用于手动执行,`cancel()` 用于取消
621
+ - **待处理状态**: `isPending()` 检查防抖调用是否排队
622
+ - **自定义执行**: 灵活的 execute 函数用于任何查询操作
623
+
624
+ ### Fetcher Hooks
625
+
626
+ #### useFetcher
627
+
628
+ `useFetcher` hook 提供完整的数据获取功能,具有自动状态管理、竞态条件保护和灵活的配置选项。它包含从 `useExecutePromise` 继承的内置 AbortController 支持。
629
+
630
+ ```typescript jsx
631
+ import { useFetcher } from '@ahoo-wang/fetcher-react';
632
+
633
+ const MyComponent = () => {
634
+ const { loading, error, result, execute, abort } = useFetcher<string>({
635
+ onAbort: () => {
636
+ console.log('获取操作已被中止');
637
+ }
638
+ });
639
+
640
+ const handleFetch = () => {
641
+ execute({ url: '/api/users', method: 'GET' });
642
+ };
643
+
644
+ const handleAbort = () => {
645
+ abort(); // 取消当前获取操作
646
+ };
647
+ ```
648
+
649
+ #### 自动执行示例
650
+
651
+ ```typescript jsx
652
+ import { useListQuery } from '@ahoo-wang/fetcher-react';
653
+
654
+ const MyComponent = () => {
655
+ const { result, loading, error, execute, setCondition } = useListQuery({
656
+ initialQuery: { condition: {}, projection: {}, sort: [], limit: 10 },
657
+ list: async (listQuery) => fetchListData(listQuery),
658
+ autoExecute: true, // 组件挂载时自动执行
659
+ });
660
+
661
+ // 查询将在组件挂载时自动执行
662
+ // 您仍然可以使用 execute() 手动触发或更新条件
663
+
664
+ if (loading) return <div>加载中...</div>;
665
+ if (error) return <div>错误: {error.message}</div>;
666
+
667
+ return (
668
+ <div>
669
+ <ul>
670
+ {result?.map((item, index) => (
671
+ <li key={index}>{item.name}</li>
672
+ ))}
673
+ </ul>
674
+ </div>
675
+ );
676
+ };
677
+ ```
678
+
679
+ #### useFetcherQuery
680
+
681
+ `useFetcherQuery` hook 提供构建与 Fetcher 库集成的专用查询 hooks 的基础。
682
+
683
+ ```typescript jsx
684
+ import { useFetcherQuery } from '@ahoo-wang/fetcher-react';
685
+
686
+ const MyComponent = () => {
687
+ const { data, loading, error, execute } = useFetcherQuery({
688
+ url: '/api/data',
689
+ initialQuery: { /* 查询参数 */ },
690
+ execute: async (query) => {
691
+ // 自定义执行逻辑
692
+ return fetchData(query);
693
+ },
694
+ autoExecute: true,
695
+ });
696
+
697
+ if (loading) return <div>加载中...</div>;
698
+ if (error) return <div>错误: {error.message}</div>;
699
+
700
+ return (
701
+ <div>
702
+ <pre>{JSON.stringify(data, null, 2)}</pre>
703
+ </div>
704
+ );
705
+ };
706
+ ```
707
+
708
+ ### 防抖 Fetcher Hooks
709
+
710
+ #### useDebouncedFetcher
711
+
712
+ 专门的 hook,将 HTTP 获取与防抖结合,基于核心 fetcher 库构建。
713
+
714
+ ```typescript jsx
715
+ import { useDebouncedFetcher } from '@ahoo-wang/fetcher-react';
716
+
717
+ const SearchInput = () => {
718
+ const [query, setQuery] = useState('');
719
+ const { loading, result, error, run } = useDebouncedFetcher({
720
+ debounce: { delay: 300 },
721
+ onSuccess: (data) => {
722
+ setSearchResults(data.results);
723
+ }
724
+ });
725
+
726
+ const handleChange = (value: string) => {
727
+ setQuery(value);
728
+ if (value.trim()) {
729
+ run({
730
+ url: '/api/search',
731
+ method: 'GET',
732
+ params: { q: value }
733
+ });
734
+ }
735
+ };
736
+
737
+ return (
738
+ <div>
739
+ <input
740
+ value={query}
741
+ onChange={(e) => handleChange(e.target.value)}
742
+ placeholder="搜索..."
743
+ />
744
+ {loading && <div>搜索中...</div>}
745
+ {error && <div>错误: {error.message}</div>}
746
+ {result && <SearchResults data={result} />}
747
+ </div>
748
+ );
749
+ };
750
+ ```
751
+
752
+ **防抖策略:**
753
+
754
+ - **前缘**: 第一次调用时立即执行,然后对后续调用进行防抖
755
+ - **后缘**: 最后一次调用后延迟执行(默认行为)
756
+ - **前缘 + 后缘**: 立即执行,如果再次调用则在延迟后再次执行
757
+
758
+ #### useDebouncedFetcherQuery
759
+
760
+ 将基于查询的 HTTP 获取与防抖结合,非常适合搜索输入和动态查询场景,您希望根据查询参数防抖 API 调用。
761
+
762
+ ```typescript jsx
763
+ import { useDebouncedFetcherQuery } from '@ahoo-wang/fetcher-react';
764
+
765
+ interface SearchQuery {
766
+ keyword: string;
767
+ limit: number;
768
+ filters?: { category?: string };
769
+ }
770
+
771
+ interface SearchResult {
772
+ items: Array<{ id: string; title: string }>;
773
+ total: number;
774
+ }
775
+
776
+ const SearchComponent = () => {
777
+ const {
778
+ loading,
779
+ result,
780
+ error,
781
+ run,
782
+ cancel,
783
+ isPending,
784
+ setQuery,
785
+ getQuery,
786
+ } = useDebouncedFetcherQuery<SearchQuery, SearchResult>({
787
+ url: '/api/search',
788
+ initialQuery: { keyword: '', limit: 10 },
789
+ debounce: { delay: 300 }, // 防抖 300ms
790
+ autoExecute: false, // 挂载时不执行
791
+ });
792
+
793
+ const handleSearch = (keyword: string) => {
794
+ setQuery({ keyword, limit: 10 }); // 如果 autoExecute 为 true,这将触发防抖执行
795
+ };
796
+
797
+ const handleManualSearch = () => {
798
+ run(); // 使用当前查询手动防抖执行
799
+ };
800
+
801
+ const handleCancel = () => {
802
+ cancel(); // 取消任何待处理的防抖执行
803
+ };
804
+
805
+ if (loading) return <div>搜索中...</div>;
806
+ if (error) return <div>错误: {error.message}</div>;
807
+
808
+ return (
809
+ <div>
810
+ <input
811
+ type="text"
812
+ onChange={(e) => handleSearch(e.target.value)}
813
+ placeholder="搜索..."
814
+ />
815
+ <button onClick={handleManualSearch} disabled={isPending()}>
816
+ {isPending() ? '搜索中...' : '搜索'}
817
+ </button>
818
+ <button onClick={handleCancel}>取消</button>
819
+ {result && (
820
+ <div>
821
+ 找到 {result.total} 项:
822
+ {result.items.map(item => (
823
+ <div key={item.id}>{item.title}</div>
824
+ ))}
825
+ </div>
826
+ )}
827
+ </div>
828
+ );
829
+ };
830
+ ```
831
+
832
+ **主要特性:**
833
+
834
+ - **查询状态管理**: 使用 `setQuery` 和 `getQuery` 自动查询参数处理
835
+ - **防抖执行**: 在快速用户输入期间防止过多 API 调用
836
+ - **自动执行**: 可选的在查询参数更改时自动执行
837
+ - **手动控制**: `run()` 用于手动执行,`cancel()` 用于取消
838
+ - **待处理状态**: `isPending()` 检查防抖调用是否排队
839
+
840
+ ### 存储 Hooks
841
+
842
+ #### useKeyStorage
843
+
844
+ `useKeyStorage` hook 为 KeyStorage 实例提供响应式状态管理。它订阅存储更改并返回当前值以及设置器函数。可选择接受默认值以在存储为空时使用。
845
+
846
+ ```typescript jsx
847
+ import { KeyStorage } from '@ahoo-wang/fetcher-storage';
848
+ import { useKeyStorage } from '@ahoo-wang/fetcher-react';
849
+
850
+ const MyComponent = () => {
851
+ const keyStorage = new KeyStorage<string>({ key: 'my-key' });
852
+
853
+ // 不使用默认值 - 可能为 null
854
+ const [value, setValue] = useKeyStorage(keyStorage);
855
+
856
+ return (
857
+ <div>
858
+ <p>当前值: {value || '无存储值'}</p>
859
+ <button onClick={() => setValue('new value')}>
860
+ 更新值
861
+ </button>
862
+ </div>
863
+ );
864
+ };
865
+ ```
866
+
867
+ #### 使用默认值
868
+
869
+ ```typescript jsx
870
+ const MyComponent = () => {
871
+ const keyStorage = new KeyStorage<string>({ key: 'theme' });
872
+
873
+ // 使用默认值 - 保证不为 null
874
+ const [theme, setTheme] = useKeyStorage(keyStorage, 'light');
875
+
876
+ return (
877
+ <div className={theme}>
878
+ <p>当前主题: {theme}</p>
879
+ <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
880
+ 切换主题
881
+ </button>
882
+ </div>
883
+ );
884
+ };
885
+ ```
886
+
887
+ #### 更多示例
888
+
889
+ ```typescript jsx
890
+ // 处理不同类型的值
891
+ const numberStorage = new KeyStorage<number>({ key: 'counter' });
892
+ const [count, setCount] = useKeyStorage(numberStorage, 0); // 默认为 0
893
+
894
+ // 处理对象
895
+ interface User {
896
+ id: string;
897
+ name: string;
898
+ }
899
+
900
+ const userStorage = new KeyStorage<User>({ key: 'current-user' });
901
+ const [user, setUser] = useKeyStorage(userStorage, { id: '', name: '访客' });
902
+
903
+ // 复杂状态管理
904
+ const settingsStorage = new KeyStorage<{ volume: number; muted: boolean }>({
905
+ key: 'audio-settings',
906
+ });
907
+ const [settings, setSettings] = useKeyStorage(settingsStorage, {
908
+ volume: 50,
909
+ muted: false,
910
+ });
911
+
912
+ // 更新特定属性
913
+ const updateVolume = (newVolume: number) => {
914
+ setSettings({ ...settings, volume: newVolume });
915
+ };
916
+ ```
917
+
918
+ ### useImmerKeyStorage
919
+
920
+ 🚀 **Immer 驱动的不可变状态管理** - `useImmerKeyStorage` hook 通过集成 Immer 的 `produce` 函数扩展了 `useKeyStorage`,允许开发者以直观的"可变"方式更新存储值,同时在底层保持不可变性。非常适合复杂对象的操作,具有自动存储同步功能。
921
+
922
+ #### 主要优势
923
+
924
+ - **直观的变更语法**: 编写看起来可变的代码,但产生不可变更新
925
+ - **深度对象支持**: 轻松处理嵌套对象和数组
926
+ - **类型安全**: 完整的 TypeScript 支持和编译时错误检查
927
+ - **性能优化**: 利用 Immer 的结构共享和最小化重渲染
928
+ - **自动同步**: 变更自动持久化到存储并跨组件同步
929
+
930
+ #### 使用场景
931
+
932
+ 在需要以下情况时选择 `useImmerKeyStorage` 而不是 `useKeyStorage`:
933
+
934
+ - 更新嵌套对象属性
935
+ - 执行复杂的数组操作(push、splice 等)
936
+ - 原子性地进行多个相关变更
937
+ - 处理深度嵌套的数据结构
938
+
939
+ ```typescript jsx
940
+ import { KeyStorage } from '@ahoo-wang/fetcher-storage';
941
+ import { useImmerKeyStorage } from '@ahoo-wang/fetcher-react';
942
+
943
+ const MyComponent = () => {
944
+ const prefsStorage = new KeyStorage<{
945
+ theme: string;
946
+ volume: number;
947
+ notifications: boolean;
948
+ shortcuts: { [key: string]: string };
949
+ }>({
950
+ key: 'user-prefs'
951
+ });
952
+
953
+ // 不使用默认值 - 可能为 null
954
+ const [prefs, updatePrefs, clearPrefs] = useImmerKeyStorage(prefsStorage);
955
+
956
+ return (
957
+ <div>
958
+ <p>主题: {prefs?.theme || '默认'}</p>
959
+ <button onClick={() => updatePrefs(draft => { draft.theme = 'dark'; })}>
960
+ 切换到深色主题
961
+ </button>
962
+ <button onClick={() => updatePrefs(draft => { draft.volume += 10; })}>
963
+ 增加音量
964
+ </button>
965
+ <button onClick={clearPrefs}>
966
+ 清除偏好设置
967
+ </button>
968
+ </div>
969
+ );
970
+ };
971
+ ```
972
+
973
+ #### 使用默认值
974
+
975
+ ```typescript jsx
976
+ const AudioControls = () => {
977
+ const settingsStorage = new KeyStorage<{ volume: number; muted: boolean }>({
978
+ key: 'audio-settings'
979
+ });
980
+
981
+ // 使用默认值 - 保证不为 null
982
+ const [settings, updateSettings, resetSettings] = useImmerKeyStorage(
983
+ settingsStorage,
984
+ { volume: 50, muted: false }
985
+ );
986
+
987
+ return (
988
+ <div>
989
+ <p>音量: {settings.volume}%</p>
990
+ <button onClick={() => updateSettings(draft => {
991
+ draft.volume = Math.min(100, draft.volume + 10);
992
+ draft.muted = false;
993
+ })}>
994
+ 增加音量
995
+ </button>
996
+ <button onClick={() => updateSettings(draft => { draft.muted = !draft.muted; })}>
997
+ 切换静音
998
+ </button>
999
+ <button onClick={resetSettings}>
1000
+ 重置为默认值
1001
+ </button>
1002
+ </div>
1003
+ );
1004
+ };
1005
+ ```
1006
+
1007
+ #### 高级用法模式
1008
+
1009
+ ##### 批量更新
1010
+
1011
+ ```typescript jsx
1012
+ const updateUserProfile = () => {
1013
+ updatePrefs(draft => {
1014
+ draft.theme = 'dark';
1015
+ draft.notifications = true;
1016
+ draft.volume = 75;
1017
+ });
1018
+ };
1019
+ ```
1020
+
1021
+ ##### 数组操作
1022
+
1023
+ ```typescript jsx
1024
+ const todoStorage = new KeyStorage<{
1025
+ todos: Array<{ id: number; text: string; done: boolean }>;
1026
+ }>({
1027
+ key: 'todos',
1028
+ });
1029
+
1030
+ const [state, updateState] = useImmerKeyStorage(todoStorage, { todos: [] });
1031
+
1032
+ // 添加新待办事项
1033
+ const addTodo = (text: string) => {
1034
+ updateState(draft => {
1035
+ draft.todos.push({
1036
+ id: Date.now(),
1037
+ text,
1038
+ done: false,
1039
+ });
1040
+ });
1041
+ };
1042
+
1043
+ // 切换待办事项状态
1044
+ const toggleTodo = (id: number) => {
1045
+ updateState(draft => {
1046
+ const todo = draft.todos.find(t => t.id === id);
1047
+ if (todo) {
1048
+ todo.done = !todo.done;
1049
+ }
1050
+ });
1051
+ };
1052
+
1053
+ // 清除已完成的待办事项
1054
+ const clearCompleted = () => {
1055
+ updateState(draft => {
1056
+ draft.todos = draft.todos.filter(todo => !todo.done);
1057
+ });
1058
+ };
1059
+ ```
1060
+
1061
+ ##### 嵌套对象更新
1062
+
1063
+ ```typescript jsx
1064
+ const configStorage = new KeyStorage<{
1065
+ ui: { theme: string; language: string };
1066
+ features: { [key: string]: boolean };
1067
+ }>({
1068
+ key: 'app-config',
1069
+ });
1070
+
1071
+ const [config, updateConfig] = useImmerKeyStorage(configStorage, {
1072
+ ui: { theme: 'light', language: 'zh' },
1073
+ features: {},
1074
+ });
1075
+
1076
+ // 更新嵌套属性
1077
+ const updateTheme = (theme: string) => {
1078
+ updateConfig(draft => {
1079
+ draft.ui.theme = theme;
1080
+ });
1081
+ };
1082
+
1083
+ const toggleFeature = (feature: string) => {
1084
+ updateConfig(draft => {
1085
+ draft.features[feature] = !draft.features[feature];
1086
+ });
1087
+ };
1088
+ ```
1089
+
1090
+ ##### 带验证的条件更新
1091
+
1092
+ ```typescript jsx
1093
+ const updateVolume = (newVolume: number) => {
1094
+ updateSettings(draft => {
1095
+ if (newVolume >= 0 && newVolume <= 100) {
1096
+ draft.volume = newVolume;
1097
+ draft.muted = false; // 音量改变时取消静音
1098
+ }
1099
+ });
1100
+ };
1101
+ ```
1102
+
1103
+ ##### 返回新值
1104
+
1105
+ ```typescript jsx
1106
+ // 替换整个状态
1107
+ const resetToFactorySettings = () => {
1108
+ updateSettings(() => ({ volume: 50, muted: false }));
1109
+ };
1110
+
1111
+ // 计算更新
1112
+ const setMaxVolume = () => {
1113
+ updateSettings(draft => ({ ...draft, volume: 100, muted: false }));
1114
+ };
1115
+ ```
1116
+
1117
+ ##### 错误处理
1118
+
1119
+ ```typescript jsx
1120
+ const safeUpdate = (updater: (draft: any) => void) => {
1121
+ try {
1122
+ updatePrefs(updater);
1123
+ } catch (error) {
1124
+ console.error('更新偏好设置失败:', error);
1125
+ // 适当处理错误
1126
+ }
1127
+ };
1128
+ ```
1129
+
1130
+ #### 最佳实践
1131
+
1132
+ ##### ✅ 推荐做法
1133
+
1134
+ - 用于复杂对象更新和数组操作
1135
+ - 利用 Immer 的 draft 变更编写可读代码
1136
+ - 在单个更新调用中组合多个相关变更
1137
+ - 对保证非空状态使用默认值
1138
+ - 在更新函数中适当处理错误
1139
+
1140
+ ##### ❌ 避免做法
1141
+
1142
+ - 不要直接用赋值修改 draft 参数(`draft = newValue`)
1143
+ - 不要在更新函数中执行副作用
1144
+ - 不要依赖对象比较的引用相等性
1145
+ - 不要用于简单的原始值更新(应使用 `useKeyStorage`)
1146
+
1147
+ ##### 性能提示
1148
+
1149
+ - 将相关更新批量处理以最小化存储操作
1150
+ - 当新状态依赖于之前状态时使用函数式更新
1151
+ - 如果更新函数经常重新创建,考虑使用 `useCallback`
1152
+ - 如果处理非常大的对象,请分析更新性能
1153
+
1154
+ ##### TypeScript 集成
1155
+
1156
+ ```typescript jsx
1157
+ // 为更好的安全性定义严格类型
1158
+ type UserPreferences = {
1159
+ theme: 'light' | 'dark' | 'auto';
1160
+ volume: number; // 0-100
1161
+ notifications: boolean;
1162
+ shortcuts: Record<string, string>;
1163
+ };
1164
+
1165
+ const prefsStorage = new KeyStorage<UserPreferences>({
1166
+ key: 'user-prefs',
1167
+ });
1168
+
1169
+ // TypeScript 将捕获无效更新
1170
+ const [prefs, updatePrefs] = useImmerKeyStorage(prefsStorage);
372
1171
 
373
1172
  // 这将导致 TypeScript 错误:
374
1173
  // updatePrefs(draft => { draft.theme = 'invalid'; });
375
1174
  ```
376
1175
 
377
- ## Wow 查询 Hooks
1176
+ ### 事件 Hooks
1177
+
1178
+ #### useEventSubscription
1179
+
1180
+ `useEventSubscription` hook 为类型化事件总线提供了 React 接口。它自动管理订阅生命周期,同时提供手动控制功能以增加灵活性。
1181
+
1182
+ ```typescript jsx
1183
+ import { useEventSubscription } from '@ahoo-wang/fetcher-react';
1184
+ import { eventBus } from './eventBus';
1185
+
1186
+ function MyComponent() {
1187
+ const { subscribe, unsubscribe } = useEventSubscription({
1188
+ bus: eventBus,
1189
+ handler: {
1190
+ name: 'myEvent',
1191
+ handle: (event) => {
1192
+ console.log('收到事件:', event);
1193
+ }
1194
+ }
1195
+ });
1196
+
1197
+ // hook 在组件挂载时自动订阅,在卸载时自动取消订阅
1198
+ // 如需要,您也可以手动控制订阅
1199
+ const handleToggleSubscription = () => {
1200
+ if (someCondition) {
1201
+ subscribe();
1202
+ } else {
1203
+ unsubscribe();
1204
+ }
1205
+ };
1206
+
1207
+ return <div>我的组件</div>;
1208
+ }
1209
+ ```
1210
+
1211
+ 关键特性:
1212
+
1213
+ - **自动生命周期管理**: 在组件挂载时自动订阅,在卸载时自动取消订阅
1214
+ - **手动控制**: 提供 `subscribe` 和 `unsubscribe` 函数以进行额外控制
1215
+ - **类型安全**: 完全支持 TypeScript,具有泛型事件类型
1216
+ - **错误处理**: 对失败的订阅尝试记录警告
1217
+ - **事件总线集成**: 与 `@ahoo-wang/fetcher-eventbus` TypedEventBus 实例无缝配合
1218
+
1219
+ ### CoSec 安全 Hooks
1220
+
1221
+ 🛡️ **企业安全集成** - 强大的 React hooks,用于使用 CoSec 令牌管理认证状态,提供与企业安全系统的无缝集成和自动令牌生命周期管理。
1222
+
1223
+ #### useSecurity
1224
+
1225
+ `useSecurity` hook 使用 CoSec 令牌提供对认证状态和操作的响应式访问。它与 TokenStorage 集成以持久化令牌,并在令牌更改时响应式更新状态。
1226
+
1227
+ ```typescript jsx
1228
+ import { useSecurity } from '@ahoo-wang/fetcher-react';
1229
+ import { tokenStorage } from './tokenStorage';
1230
+ import { useNavigate } from 'react-router-dom';
1231
+
1232
+ function App() {
1233
+ const navigate = useNavigate();
1234
+
1235
+ const { currentUser, authenticated, signIn, signOut } = useSecurity(tokenStorage, {
1236
+ onSignIn: () => {
1237
+ // 登录成功后重定向到仪表板
1238
+ navigate('/dashboard');
1239
+ },
1240
+ onSignOut: () => {
1241
+ // 登出后重定向到登录页面
1242
+ navigate('/login');
1243
+ }
1244
+ });
1245
+
1246
+ const handleSignIn = async () => {
1247
+ // 直接令牌
1248
+ await signIn(compositeToken);
1249
+
1250
+ // 或异步函数
1251
+ await signIn(async () => {
1252
+ const response = await fetch('/api/auth/login', {
1253
+ method: 'POST',
1254
+ body: JSON.stringify({ username, password })
1255
+ });
1256
+ return response.json();
1257
+ });
1258
+ };
1259
+
1260
+ if (!authenticated) {
1261
+ return <button onClick={handleSignIn}>登录</button>;
1262
+ }
1263
+
1264
+ return (
1265
+ <div>
1266
+ <p>欢迎, {currentUser.sub}!</p>
1267
+ <button onClick={signOut}>登出</button>
1268
+ </div>
1269
+ );
1270
+ }
1271
+ ```
1272
+
1273
+ **关键特性:**
1274
+
1275
+ - **响应式认证状态**: 当令牌更改时自动更新
1276
+ - **灵活的登录方法**: 支持直接令牌和异步令牌提供者
1277
+ - **生命周期回调**: 可配置的登录和登出事件回调
1278
+ - **类型安全**: 完全支持 TypeScript,具有 CoSec JWT 负载类型
1279
+ - **令牌持久化**: 与 TokenStorage 集成以实现跨会话持久化
1280
+
1281
+ #### SecurityProvider
1282
+
1283
+ `SecurityProvider` 组件包装您的应用程序以通过 React 上下文提供认证上下文。它在内部使用 `useSecurity` hook,并通过 `useSecurityContext` hook 使认证状态可用于所有子组件。
1284
+
1285
+ ```tsx
1286
+ import { SecurityProvider } from '@ahoo-wang/fetcher-react';
1287
+ import { tokenStorage } from './tokenStorage';
1288
+ import { useNavigate } from 'react-router-dom';
1289
+
1290
+ function App() {
1291
+ const navigate = useNavigate();
1292
+
1293
+ return (
1294
+ <SecurityProvider
1295
+ tokenStorage={tokenStorage}
1296
+ onSignIn={() => navigate('/dashboard')}
1297
+ onSignOut={() => navigate('/login')}
1298
+ >
1299
+ <MyApp />
1300
+ </SecurityProvider>
1301
+ );
1302
+ }
1303
+ ```
1304
+
1305
+ **配置选项:**
1306
+
1307
+ - `tokenStorage`: 用于管理认证令牌的 TokenStorage 实例
1308
+ - `onSignIn`: 登录成功时调用的回调函数
1309
+ - `onSignOut`: 登出时调用的回调函数
1310
+ - `children`: 将有权访问安全上下文的子组件
1311
+
1312
+ #### useSecurityContext
1313
+
1314
+ `useSecurityContext` hook 在被 `SecurityProvider` 包装的组件中提供对认证状态和方法的访问。它通过 React 上下文提供与 `useSecurity` 相同的接口。
1315
+
1316
+ ```tsx
1317
+ import { useSecurityContext } from '@ahoo-wang/fetcher-react';
1318
+
1319
+ function UserProfile() {
1320
+ const { currentUser, authenticated, signOut } = useSecurityContext();
1321
+
1322
+ if (!authenticated) {
1323
+ return <div>请登录</div>;
1324
+ }
1325
+
1326
+ return (
1327
+ <div>
1328
+ <p>欢迎, {currentUser.sub}!</p>
1329
+ <button onClick={signOut}>登出</button>
1330
+ </div>
1331
+ );
1332
+ }
1333
+ ```
1334
+
1335
+ **上下文优势:**
1336
+
1337
+ - **消除属性钻取**: 无需传递属性即可访问认证状态
1338
+ - **组件隔离**: 无论组件树深度如何,组件都可以访问认证状态
1339
+ - **集中式状态**: 应用程序中认证的单一真实来源
1340
+ - **自动重新渲染**: 当认证状态更改时,组件自动重新渲染
1341
+
1342
+ ### Wow 查询 Hooks
378
1343
 
379
1344
  Wow 查询 Hooks 提供高级数据查询功能,具有内置的状态管理,用于条件、投影、排序、分页和限制。这些 hooks 专为与 `@ahoo-wang/fetcher-wow` 包配合使用而设计,用于复杂的查询操作。
380
1345
 
381
- ### 基础查询 Hooks
1346
+ #### 基础查询 Hooks
382
1347
 
383
- #### useListQuery Hook
1348
+ ##### useListQuery
384
1349
 
385
1350
  `useListQuery` hook 管理列表查询,具有条件、投影、排序和限制的状态管理。
386
1351
 
@@ -391,7 +1356,7 @@ const MyComponent = () => {
391
1356
  const { result, loading, error, execute, setCondition, setLimit } = useListQuery({
392
1357
  initialQuery: { condition: {}, projection: {}, sort: [], limit: 10 },
393
1358
  execute: async (listQuery) => {
394
- // Your list fetching logic here
1359
+ // 您的列表获取逻辑
395
1360
  return fetchListData(listQuery);
396
1361
  },
397
1362
  });
@@ -417,7 +1382,37 @@ const MyComponent = () => {
417
1382
  };
418
1383
  ```
419
1384
 
420
- ### usePagedQuery Hook
1385
+ ##### 自动执行示例
1386
+
1387
+ ```typescript jsx
1388
+ import { useListQuery } from '@ahoo-wang/fetcher-react';
1389
+
1390
+ const MyComponent = () => {
1391
+ const { result, loading, error, execute, setCondition } = useListQuery({
1392
+ initialQuery: { condition: {}, projection: {}, sort: [], limit: 10 },
1393
+ execute: async (listQuery) => fetchListData(listQuery),
1394
+ autoExecute: true, // 组件挂载时自动执行
1395
+ });
1396
+
1397
+ // 查询将在组件挂载时自动执行
1398
+ // 您仍然可以使用 execute() 手动触发或更新条件
1399
+
1400
+ if (loading) return <div>加载中...</div>;
1401
+ if (error) return <div>错误: {error.message}</div>;
1402
+
1403
+ return (
1404
+ <div>
1405
+ <ul>
1406
+ {result?.map((item, index) => (
1407
+ <li key={index}>{item.name}</li>
1408
+ ))}
1409
+ </ul>
1410
+ </div>
1411
+ );
1412
+ };
1413
+ ```
1414
+
1415
+ ##### usePagedQuery
421
1416
 
422
1417
  `usePagedQuery` hook 管理分页查询,具有条件、投影、分页和排序的状态管理。
423
1418
 
@@ -433,7 +1428,7 @@ const MyComponent = () => {
433
1428
  sort: []
434
1429
  },
435
1430
  execute: async (pagedQuery) => {
436
- // Your paged fetching logic here
1431
+ // 您的分页获取逻辑
437
1432
  return fetchPagedData(pagedQuery);
438
1433
  },
439
1434
  });
@@ -448,108 +1443,285 @@ const MyComponent = () => {
448
1443
 
449
1444
  return (
450
1445
  <div>
451
- <ul>
452
- {result?.list?.map((item, index) => (
453
- <li key={index}>{item.name}</li>
454
- ))}
455
- </ul>
456
- <button onClick={() => handlePageChange(result?.pagination?.index! - 1)} disabled={result?.pagination?.index === 1}>
457
- 上一页
458
- </button>
459
- <button onClick={() => handlePageChange(result?.pagination?.index! + 1)}>
460
- 下一页
461
- </button>
1446
+ <ul>
1447
+ {result?.list?.map((item, index) => (
1448
+ <li key={index}>{item.name}</li>
1449
+ ))}
1450
+ </ul>
1451
+ <button onClick={() => handlePageChange(result?.pagination?.index! - 1)} disabled={result?.pagination?.index === 1}>
1452
+ 上一页
1453
+ </button>
1454
+ <button onClick={() => handlePageChange(result?.pagination?.index! + 1)}>
1455
+ 下一页
1456
+ </button>
1457
+ </div>
1458
+ );
1459
+ };
1460
+ ```
1461
+
1462
+ ###### 自动执行示例
1463
+
1464
+ ```typescript jsx
1465
+ import { usePagedQuery } from '@ahoo-wang/fetcher-react';
1466
+
1467
+ const MyComponent = () => {
1468
+ const { result, loading, error, execute, setCondition, setPagination } = usePagedQuery({
1469
+ initialQuery: {
1470
+ condition: {},
1471
+ pagination: { index: 1, size: 10 },
1472
+ projection: {},
1473
+ sort: []
1474
+ },
1475
+ execute: async (pagedQuery) => fetchPagedData(pagedQuery),
1476
+ autoExecute: true, // 组件挂载时自动执行
1477
+ });
1478
+
1479
+ // 查询将在组件挂载时自动执行
1480
+
1481
+ if (loading) return <div>加载中...</div>;
1482
+ if (error) return <div>错误: {error.message}</div>;
1483
+
1484
+ return (
1485
+ <div>
1486
+ <ul>
1487
+ {result?.list?.map((item, index) => (
1488
+ <li key={index}>{item.name}</li>
1489
+ ))}
1490
+ </ul>
1491
+ <button onClick={() => setPagination({ index: result?.pagination?.index! - 1, size: 10 })} disabled={result?.pagination?.index === 1}>
1492
+ 上一页
1493
+ </button>
1494
+ <button onClick={() => setPagination({ index: result?.pagination?.index! + 1, size: 10 })}>
1495
+ 下一页
1496
+ </button>
1497
+ </div>
1498
+ );
1499
+ };
1500
+ ```
1501
+
1502
+ ##### useSingleQuery
1503
+
1504
+ `useSingleQuery` hook 管理单个查询,具有条件、投影和排序的状态管理。
1505
+
1506
+ ```typescript jsx
1507
+ import { useSingleQuery } from '@ahoo-wang/fetcher-react';
1508
+
1509
+ const MyComponent = () => {
1510
+ const { result, loading, error, execute, setCondition } = useSingleQuery({
1511
+ initialQuery: { condition: {}, projection: {}, sort: [] },
1512
+ execute: async (singleQuery) => {
1513
+ // 您的单个获取逻辑
1514
+ return fetchSingleData(singleQuery);
1515
+ },
1516
+ });
1517
+
1518
+ const handleFetchUser = (userId: string) => {
1519
+ setCondition({ id: userId });
1520
+ execute();
1521
+ };
1522
+
1523
+ if (loading) return <div>加载中...</div>;
1524
+ if (error) return <div>错误: {error.message}</div>;
1525
+
1526
+ return (
1527
+ <div>
1528
+ <button onClick={() => handleFetchUser('123')}>获取用户</button>
1529
+ {result && <p>用户: {result.name}</p>}
1530
+ </div>
1531
+ );
1532
+ };
1533
+ ```
1534
+
1535
+ ###### 自动执行示例
1536
+
1537
+ ```typescript jsx
1538
+ import { useSingleQuery } from '@ahoo-wang/fetcher-react';
1539
+
1540
+ const MyComponent = () => {
1541
+ const { result, loading, error, execute, setCondition } = useSingleQuery({
1542
+ initialQuery: { condition: {}, projection: {}, sort: [] },
1543
+ execute: async (singleQuery) => fetchSingleData(singleQuery),
1544
+ autoExecute: true, // 组件挂载时自动执行
1545
+ });
1546
+
1547
+ // 查询将在组件挂载时自动执行
1548
+
1549
+ if (loading) return <div>加载中...</div>;
1550
+ if (error) return <div>错误: {error.message}</div>;
1551
+
1552
+ return (
1553
+ <div>
1554
+ {result && <p>用户: {result.name}</p>}
1555
+ </div>
1556
+ );
1557
+ };
1558
+ ```
1559
+
1560
+ ##### useCountQuery
1561
+
1562
+ `useCountQuery` hook 管理计数查询,具有条件的状态管理。
1563
+
1564
+ ```typescript jsx
1565
+ import { useCountQuery } from '@ahoo-wang/fetcher-react';
1566
+
1567
+ const MyComponent = () => {
1568
+ const { result, loading, error, execute, setCondition } = useCountQuery({
1569
+ initialQuery: {},
1570
+ execute: async (condition) => {
1571
+ // 您的计数获取逻辑
1572
+ return fetchCount(condition);
1573
+ },
1574
+ });
1575
+
1576
+ const handleCountActive = () => {
1577
+ setCondition({ status: 'active' });
1578
+ execute();
1579
+ };
1580
+
1581
+ if (loading) return <div>加载中...</div>;
1582
+ if (error) return <div>错误: {error.message}</div>;
1583
+
1584
+ return (
1585
+ <div>
1586
+ <button onClick={handleCountActive}>计数活跃项目</button>
1587
+ <p>总数: {result}</p>
1588
+ </div>
1589
+ );
1590
+ };
1591
+ ```
1592
+
1593
+ ###### 自动执行示例
1594
+
1595
+ ```typescript jsx
1596
+ import { useCountQuery } from '@ahoo-wang/fetcher-react';
1597
+
1598
+ const MyComponent = () => {
1599
+ const { result, loading, error, execute, setCondition } = useCountQuery({
1600
+ initialQuery: {},
1601
+ execute: async (condition) => fetchCount(condition),
1602
+ autoExecute: true, // 组件挂载时自动执行
1603
+ });
1604
+
1605
+ // 查询将在组件挂载时自动执行
1606
+
1607
+ if (loading) return <div>加载中...</div>;
1608
+ if (error) return <div>错误: {error.message}</div>;
1609
+
1610
+ return (
1611
+ <div>
1612
+ <p>总数: {result}</p>
462
1613
  </div>
463
1614
  );
464
1615
  };
465
1616
  ```
466
1617
 
467
- ### useSingleQuery Hook
1618
+ ##### useListStreamQuery
468
1619
 
469
- `useSingleQuery` hook 管理单个查询,具有条件、投影和排序的状态管理。
1620
+ `useListStreamQuery` hook 管理列表流查询,返回服务器发送事件的 readable stream。
470
1621
 
471
1622
  ```typescript jsx
472
- import { useSingleQuery } from '@ahoo-wang/fetcher-react';
1623
+ import { useListStreamQuery } from '@ahoo-wang/fetcher-react';
473
1624
 
474
1625
  const MyComponent = () => {
475
- const { result, loading, error, execute, setCondition } = useSingleQuery({
476
- initialQuery: { condition: {}, projection: {}, sort: [] },
477
- execute: async (singleQuery) => {
478
- // 您的单个获取逻辑
479
- return fetchSingleData(singleQuery);
1626
+ const { result, loading, error, execute, setCondition } = useListStreamQuery({
1627
+ initialQuery: { condition: {}, projection: {}, sort: [], limit: 100 },
1628
+ execute: async (listQuery) => {
1629
+ // 您的流获取逻辑
1630
+ return fetchListStream(listQuery);
480
1631
  },
481
1632
  });
482
1633
 
483
- const handleFetchUser = (userId: string) => {
484
- setCondition({ id: userId });
485
- execute();
486
- };
1634
+ useEffect(() => {
1635
+ if (result) {
1636
+ const reader = result.getReader();
1637
+ const readStream = async () => {
1638
+ try {
1639
+ while (true) {
1640
+ const { done, value } = await reader.read();
1641
+ if (done) break;
1642
+ console.log('接收到:', value);
1643
+ // 处理流事件
1644
+ }
1645
+ } catch (error) {
1646
+ console.error('流错误:', error);
1647
+ }
1648
+ };
1649
+ readStream();
1650
+ }
1651
+ }, [result]);
487
1652
 
488
1653
  if (loading) return <div>加载中...</div>;
489
1654
  if (error) return <div>错误: {error.message}</div>;
490
1655
 
491
1656
  return (
492
1657
  <div>
493
- <button onClick={() => handleFetchUser('123')}>获取用户</button>
494
- {result && <p>用户: {result.name}</p>}
1658
+ <button onClick={execute}>开始流</button>
495
1659
  </div>
496
1660
  );
497
1661
  };
498
1662
  ```
499
1663
 
500
- ### useCountQuery Hook
501
-
502
- `useCountQuery` hook 管理计数查询,具有条件的状态管理。
1664
+ ###### 自动执行示例
503
1665
 
504
1666
  ```typescript jsx
505
- import { useCountQuery } from '@ahoo-wang/fetcher-react';
1667
+ import { useListStreamQuery } from '@ahoo-wang/fetcher-react';
506
1668
 
507
1669
  const MyComponent = () => {
508
- const { result, loading, error, execute, setCondition } = useCountQuery({
509
- initialQuery: {},
510
- execute: async (condition) => {
511
- // 您的计数获取逻辑
512
- return fetchCount(condition);
513
- },
1670
+ const { result, loading, error, execute, setCondition } = useListStreamQuery({
1671
+ initialQuery: { condition: {}, projection: {}, sort: [], limit: 100 },
1672
+ execute: async (listQuery) => fetchListStream(listQuery),
1673
+ autoExecute: true, // 组件挂载时自动执行
514
1674
  });
515
1675
 
516
- const handleCountActive = () => {
517
- setCondition({ status: 'active' });
518
- execute();
519
- };
1676
+ useEffect(() => {
1677
+ if (result) {
1678
+ const reader = result.getReader();
1679
+ const readStream = async () => {
1680
+ try {
1681
+ while (true) {
1682
+ const { done, value } = await reader.read();
1683
+ if (done) break;
1684
+ console.log('接收到:', value);
1685
+ // 处理流事件
1686
+ }
1687
+ } catch (error) {
1688
+ console.error('流错误:', error);
1689
+ }
1690
+ };
1691
+ readStream();
1692
+ }
1693
+ }, [result]);
1694
+
1695
+ // 流将在组件挂载时自动启动
520
1696
 
521
1697
  if (loading) return <div>加载中...</div>;
522
1698
  if (error) return <div>错误: {error.message}</div>;
523
1699
 
524
1700
  return (
525
1701
  <div>
526
- <button onClick={handleCountActive}>计数活跃项目</button>
527
- <p>总数: {result}</p>
1702
+ {/* 流已自动启动 */}
528
1703
  </div>
529
1704
  );
530
1705
  };
531
1706
  ```
532
1707
 
533
- ### 获取查询 Hooks
1708
+ #### Fetcher 查询 Hooks
534
1709
 
535
- #### useFetcherCountQuery Hook
1710
+ ##### useFetcherCountQuery
536
1711
 
537
1712
  `useFetcherCountQuery` hook 是使用 Fetcher 库执行计数查询的专用 React hook。它专为需要检索匹配特定条件的记录数量的场景而设计,返回表示计数的数字。
538
1713
 
539
1714
  ```typescript jsx
540
1715
  import { useFetcherCountQuery } from '@ahoo-wang/fetcher-react';
541
1716
  import { all } from '@ahoo-wang/fetcher-wow';
542
-
543
1717
  function UserCountComponent() {
544
1718
  const { data: count, loading, error, execute } = useFetcherCountQuery({
545
1719
  url: '/api/users/count',
546
1720
  initialQuery: all(),
547
1721
  autoExecute: true,
548
1722
  });
549
-
550
1723
  if (loading) return <div>加载中...</div>;
551
1724
  if (error) return <div>错误: {error.message}</div>;
552
-
553
1725
  return (
554
1726
  <div>
555
1727
  <div>活跃用户总数: {count}</div>
@@ -559,23 +1731,19 @@ function UserCountComponent() {
559
1731
  }
560
1732
  ```
561
1733
 
562
- #### 自动执行示例
1734
+ ###### 自动执行示例
563
1735
 
564
1736
  ```typescript jsx
565
1737
  import { useFetcherCountQuery } from '@ahoo-wang/fetcher-react';
566
-
567
1738
  const MyComponent = () => {
568
1739
  const { data: count, loading, error, execute } = useFetcherCountQuery({
569
1740
  url: '/api/users/count',
570
1741
  initialQuery: { status: 'active' },
571
1742
  autoExecute: true, // 组件挂载时自动执行
572
1743
  });
573
-
574
1744
  // 查询将在组件挂载时自动执行
575
-
576
1745
  if (loading) return <div>加载中...</div>;
577
1746
  if (error) return <div>错误: {error.message}</div>;
578
-
579
1747
  return (
580
1748
  <div>
581
1749
  <p>活跃用户总数: {count}</p>
@@ -584,7 +1752,7 @@ const MyComponent = () => {
584
1752
  };
585
1753
  ```
586
1754
 
587
- ### useFetcherPagedQuery Hook
1755
+ ##### useFetcherPagedQuery
588
1756
 
589
1757
  `useFetcherPagedQuery` hook 是使用 Fetcher 库执行分页查询的专用 React hook。它专为需要检索匹配查询条件的分页数据的场景而设计,返回包含当前页面项目以及分页元数据的 PagedList。
590
1758
 
@@ -653,7 +1821,7 @@ function UserListComponent() {
653
1821
  }
654
1822
  ```
655
1823
 
656
- #### 自动执行示例
1824
+ ###### 自动执行示例
657
1825
 
658
1826
  ```typescript jsx
659
1827
  import { useFetcherPagedQuery } from '@ahoo-wang/fetcher-react';
@@ -689,7 +1857,7 @@ const MyComponent = () => {
689
1857
  };
690
1858
  ```
691
1859
 
692
- ### useFetcherListQuery Hook
1860
+ ##### useFetcherListQuery
693
1861
 
694
1862
  `useFetcherListQuery` hook 是使用 Fetcher 库执行列表查询的专用 React hook。它专为获取项目列表而设计,支持通过 ListQuery 类型进行过滤、排序和分页,返回结果数组。
695
1863
 
@@ -750,7 +1918,7 @@ function UserListComponent() {
750
1918
  }
751
1919
  ```
752
1920
 
753
- #### 自动执行示例
1921
+ ###### 自动执行示例
754
1922
 
755
1923
  ```typescript jsx
756
1924
  import { useFetcherListQuery } from '@ahoo-wang/fetcher-react';
@@ -785,7 +1953,7 @@ const MyComponent = () => {
785
1953
  };
786
1954
  ```
787
1955
 
788
- ### useFetcherListStreamQuery Hook
1956
+ ##### useFetcherListStreamQuery
789
1957
 
790
1958
  `useFetcherListStreamQuery` hook 是使用 Fetcher 库通过服务器发送事件执行列表流查询的专用 React hook。它专为需要检索匹配列表查询条件的数据流场景而设计,返回 JSON 服务器发送事件的 ReadableStream,用于实时数据流式传输。
791
1959
 
@@ -848,7 +2016,7 @@ function UserStreamComponent() {
848
2016
  }
849
2017
  ```
850
2018
 
851
- #### 自动执行示例
2019
+ ###### 自动执行示例
852
2020
 
853
2021
  ```typescript jsx
854
2022
  import { useFetcherListStreamQuery } from '@ahoo-wang/fetcher-react';
@@ -896,163 +2064,587 @@ const MyComponent = () => {
896
2064
  if (error) return <div>错误: {error.message}</div>;
897
2065
 
898
2066
  return (
899
- <div>
900
- <h2>实时通知</h2>
901
- <div ref={notificationsRef}></div>
902
- </div>
2067
+ <div>
2068
+ <h2>实时通知</h2>
2069
+ <div ref={notificationsRef}></div>
2070
+ </div>
2071
+ );
2072
+ };
2073
+ ```
2074
+
2075
+ ##### useFetcherSingleQuery
2076
+
2077
+ `useFetcherSingleQuery` hook 是使用 Fetcher 库执行单个项目查询的专用 React hook。它专为获取单个项目而设计,支持通过 SingleQuery 类型进行过滤和排序,返回单个结果项目。
2078
+
2079
+ ```typescript jsx
2080
+ import { useFetcherSingleQuery } from '@ahoo-wang/fetcher-react';
2081
+ import { singleQuery, eq } from '@ahoo-wang/fetcher-wow';
2082
+
2083
+ interface User {
2084
+ id: string;
2085
+ name: string;
2086
+ email: string;
2087
+ createdAt: string;
2088
+ }
2089
+
2090
+ function UserProfileComponent({ userId }: { userId: string }) {
2091
+ const {
2092
+ loading,
2093
+ result: user,
2094
+ error,
2095
+ execute,
2096
+ } = useFetcherSingleQuery<User, keyof User>({
2097
+ url: `/api/users/${userId}`,
2098
+ initialQuery: singleQuery({
2099
+ condition: eq('id', userId),
2100
+ }),
2101
+ autoExecute: true,
2102
+ });
2103
+
2104
+ if (loading) return <div>正在加载用户...</div>;
2105
+ if (error) return <div>错误: {error.message}</div>;
2106
+ if (!user) return <div>未找到用户</div>;
2107
+
2108
+ return (
2109
+ <div>
2110
+ <h2>{user.name}</h2>
2111
+ <p>邮箱: {user.email}</p>
2112
+ <p>创建时间: {user.createdAt}</p>
2113
+ <button onClick={execute}>刷新</button>
2114
+ </div>
2115
+ );
2116
+ }
2117
+ ```
2118
+
2119
+ ###### 自动执行示例
2120
+
2121
+ ```typescript jsx
2122
+ import { useFetcherSingleQuery } from '@ahoo-wang/fetcher-react';
2123
+
2124
+ const MyComponent = () => {
2125
+ const { result: product, loading, error, execute } = useFetcherSingleQuery({
2126
+ url: '/api/products/featured',
2127
+ initialQuery: {
2128
+ condition: { featured: true },
2129
+ projection: {},
2130
+ sort: []
2131
+ },
2132
+ autoExecute: true, // 组件挂载时自动执行
2133
+ });
2134
+
2135
+ // 查询将在组件挂载时自动执行
2136
+
2137
+ if (loading) return <div>加载中...</div>;
2138
+ if (error) return <div>错误: {error.message}</div>;
2139
+ if (!product) return <div>未找到产品</div>;
2140
+
2141
+ return (
2142
+ <div>
2143
+ <h2>特色产品</h2>
2144
+ <div>{product.name}</div>
2145
+ <div>{product.description}</div>
2146
+ </div>
2147
+ );
2148
+ };
2149
+ ```
2150
+
2151
+ ## 最佳实践
2152
+
2153
+ ### 性能优化
2154
+
2155
+ - 使用 `autoExecute: false` 来控制查询的执行时机
2156
+ - 当启用 `autoExecute` 时,使用 `setQuery` 更新查询以触发自动重新执行
2157
+ - 在 `execute` 函数中记忆化昂贵的计算
2158
+
2159
+ ### 错误处理
2160
+
2161
+ - 始终在组件中处理加载和错误状态
2162
+ - 使用自定义错误类型以更好地分类错误
2163
+ - 为瞬时故障实现重试逻辑
2164
+
2165
+ ### 类型安全
2166
+
2167
+ - 为查询参数和结果定义严格的接口
2168
+ - 在整个应用程序中一致使用泛型类型
2169
+ - 启用严格 TypeScript 模式以获得最大安全性
2170
+
2171
+ ### 状态管理
2172
+
2173
+ - 与全局状态管理结合使用(Redux、Zustand)以处理复杂应用
2174
+ - 使用 `useKeyStorage` 进行持久化的客户端数据存储
2175
+ - 实现乐观更新以改善用户体验
2176
+
2177
+ ## 🚀 高级使用示例
2178
+
2179
+ ### 自定义 Hook 组合
2180
+
2181
+ 通过组合多个 fetcher-react hooks 创建可重用的 hooks:
2182
+
2183
+ ```typescript jsx
2184
+ import { useFetcher, usePromiseState, useLatest } from '@ahoo-wang/fetcher-react';
2185
+ import { useCallback, useEffect } from 'react';
2186
+
2187
+ function useUserProfile(userId: string) {
2188
+ const latestUserId = useLatest(userId);
2189
+ const { loading, result: profile, error, execute } = useFetcher();
2190
+
2191
+ const fetchProfile = useCallback(() => {
2192
+ execute({
2193
+ url: `/api/users/${latestUserId.current}`,
2194
+ method: 'GET'
2195
+ });
2196
+ }, [execute, latestUserId]);
2197
+
2198
+ useEffect(() => {
2199
+ if (userId) {
2200
+ fetchProfile();
2201
+ }
2202
+ }, [userId, fetchProfile]);
2203
+
2204
+ return { profile, loading, error, refetch: fetchProfile };
2205
+ }
2206
+
2207
+ // 使用
2208
+ function UserProfile({ userId }: { userId: string }) {
2209
+ const { profile, loading, error, refetch } = useUserProfile(userId);
2210
+
2211
+ if (loading) return <div>加载中...</div>;
2212
+ if (error) return <div>错误: {error.message}</div>;
2213
+
2214
+ return (
2215
+ <div>
2216
+ <h2>{profile?.name}</h2>
2217
+ <button onClick={refetch}>刷新</button>
2218
+ </div>
2219
+ );
2220
+ }
2221
+ ```
2222
+
2223
+ ### 错误边界集成
2224
+
2225
+ 与 React 错误边界集成以获得更好的错误处理:
2226
+
2227
+ ```typescript jsx
2228
+ import { Component, ErrorInfo, ReactNode } from 'react';
2229
+
2230
+ class FetchErrorBoundary extends Component<
2231
+ { children: ReactNode; fallback?: ReactNode },
2232
+ { hasError: boolean; error?: Error }
2233
+ > {
2234
+ constructor(props: { children: ReactNode; fallback?: ReactNode }) {
2235
+ super(props);
2236
+ this.state = { hasError: false };
2237
+ }
2238
+
2239
+ static getDerivedStateFromError(error: Error) {
2240
+ return { hasError: true, error };
2241
+ }
2242
+
2243
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
2244
+ console.error('获取错误边界捕获到错误:', error, errorInfo);
2245
+ }
2246
+
2247
+ render() {
2248
+ if (this.state.hasError) {
2249
+ return this.props.fallback || <div>出现问题。</div>;
2250
+ }
2251
+
2252
+ return this.props.children;
2253
+ }
2254
+ }
2255
+
2256
+ // 与 hooks 一起使用
2257
+ function DataComponent() {
2258
+ const { result, loading, error, execute } = useFetcher();
2259
+
2260
+ // 错误将被边界捕获(如果抛出)
2261
+ if (error) {
2262
+ throw error;
2263
+ }
2264
+
2265
+ return (
2266
+ <div>
2267
+ {loading ? '加载中...' : JSON.stringify(result)}
2268
+ </div>
2269
+ );
2270
+ }
2271
+
2272
+ // 包装使用 fetcher hooks 的组件
2273
+ function App() {
2274
+ return (
2275
+ <FetchErrorBoundary fallback={<div>加载数据失败</div>}>
2276
+ <DataComponent />
2277
+ </FetchErrorBoundary>
903
2278
  );
904
- };
2279
+ }
905
2280
  ```
906
2281
 
907
- ### useFetcherSingleQuery Hook
2282
+ ### Suspense 集成
908
2283
 
909
- `useFetcherSingleQuery` hook 是使用 Fetcher 库执行单个项目查询的专用 React hook。它专为获取单个项目而设计,支持通过 SingleQuery 类型进行过滤和排序,返回单个结果项目。
2284
+ React Suspense 一起使用以获得更好的加载状态:
910
2285
 
911
2286
  ```typescript jsx
912
- import { useFetcherSingleQuery } from '@ahoo-wang/fetcher-react';
913
- import { singleQuery, eq } from '@ahoo-wang/fetcher-wow';
2287
+ import { Suspense, useState } from 'react';
2288
+ import { useFetcher } from '@ahoo-wang/fetcher-react';
914
2289
 
915
- interface User {
916
- id: string;
917
- name: string;
918
- email: string;
919
- createdAt: string;
2290
+ // 创建抛出 Promise 的资源
2291
+ function createDataResource<T>(promise: Promise<T>) {
2292
+ let status = 'pending';
2293
+ let result: T;
2294
+ let error: Error;
2295
+
2296
+ const suspender = promise.then(
2297
+ (data) => {
2298
+ status = 'success';
2299
+ result = data;
2300
+ },
2301
+ (err) => {
2302
+ status = 'error';
2303
+ error = err;
2304
+ }
2305
+ );
2306
+
2307
+ return {
2308
+ read() {
2309
+ if (status === 'pending') {
2310
+ throw suspender;
2311
+ } else if (status === 'error') {
2312
+ throw error;
2313
+ } else {
2314
+ return result;
2315
+ }
2316
+ }
2317
+ };
920
2318
  }
921
2319
 
922
- function UserProfileComponent({ userId }: { userId: string }) {
923
- const {
924
- loading,
925
- result: user,
926
- error,
927
- execute,
928
- } = useFetcherSingleQuery<User, keyof User>({
929
- url: `/api/users/${userId}`,
930
- initialQuery: singleQuery({
931
- condition: eq('id', userId),
932
- }),
933
- autoExecute: true,
934
- });
2320
+ function DataComponent({ resource }: { resource: any }) {
2321
+ const data = resource.read(); // 如果待处理将抛出
2322
+ return <div>{JSON.stringify(data)}</div>;
2323
+ }
935
2324
 
936
- if (loading) return <div>正在加载用户...</div>;
937
- if (error) return <div>错误: {error.message}</div>;
938
- if (!user) return <div>未找到用户</div>;
2325
+ function App() {
2326
+ const [resource, setResource] = useState<any>(null);
2327
+
2328
+ const handleFetch = () => {
2329
+ const { execute } = useFetcher();
2330
+ const promise = execute({ url: '/api/data', method: 'GET' });
2331
+ setResource(createDataResource(promise));
2332
+ };
939
2333
 
940
2334
  return (
941
2335
  <div>
942
- <h2>{user.name}</h2>
943
- <p>邮箱: {user.email}</p>
944
- <p>创建时间: {user.createdAt}</p>
945
- <button onClick={execute}>刷新</button>
2336
+ <button onClick={handleFetch}>获取数据</button>
2337
+ <Suspense fallback={<div>加载中...</div>}>
2338
+ {resource && <DataComponent resource={resource} />}
2339
+ </Suspense>
946
2340
  </div>
947
2341
  );
948
2342
  }
949
2343
  ```
950
2344
 
951
- #### 自动执行示例
2345
+ ### 性能优化模式
2346
+
2347
+ 优化性能的高级模式:
952
2348
 
953
2349
  ```typescript jsx
954
- import { useFetcherSingleQuery } from '@ahoo-wang/fetcher-react';
2350
+ import { useMemo, useCallback, useRef } from 'react';
2351
+ import { useListQuery } from '@ahoo-wang/fetcher-react';
955
2352
 
956
- const MyComponent = () => {
957
- const { result: product, loading, error, execute } = useFetcherSingleQuery({
958
- url: '/api/products/featured',
959
- initialQuery: {
960
- condition: { featured: true },
961
- projection: {},
962
- sort: []
963
- },
964
- autoExecute: true, // 组件挂载时自动执行
2353
+ function OptimizedDataTable({ filters, sortBy }) {
2354
+ // 记忆化查询配置以防止不必要的重新执行
2355
+ const queryConfig = useMemo(() => ({
2356
+ condition: filters,
2357
+ sort: [{ field: sortBy, order: 'asc' }],
2358
+ limit: 50
2359
+ }), [filters, sortBy]);
2360
+
2361
+ const { result, loading, execute, setCondition } = useListQuery({
2362
+ initialQuery: queryConfig,
2363
+ execute: useCallback(async (query) => {
2364
+ // 防抖 API 调用
2365
+ await new Promise(resolve => setTimeout(resolve, 300));
2366
+ return fetchData(query);
2367
+ }, []),
2368
+ autoExecute: true
965
2369
  });
966
2370
 
967
- // 查询将在组件挂载时自动执行
2371
+ // 使用 ref 跟踪最新过滤器而不引起重新渲染
2372
+ const filtersRef = useRef(filters);
968
2373
 
969
- if (loading) return <div>加载中...</div>;
970
- if (error) return <div>错误: {error.message}</div>;
971
- if (!product) return <div>未找到产品</div>;
2374
+ useEffect(() => {
2375
+ filtersRef.current = filters;
2376
+ });
2377
+
2378
+ // 防抖搜索
2379
+ const debouncedSearch = useMemo(
2380
+ () => debounce((searchTerm: string) => {
2381
+ setCondition({ ...filtersRef.current, search: searchTerm });
2382
+ }, 500),
2383
+ [setCondition]
2384
+ );
972
2385
 
973
2386
  return (
974
2387
  <div>
975
- <h2>特色产品</h2>
976
- <div>{product.name}</div>
977
- <div>{product.description}</div>
2388
+ <input
2389
+ onChange={(e) => debouncedSearch(e.target.value)}
2390
+ placeholder="搜索..."
2391
+ />
2392
+ {loading ? '加载中...' : (
2393
+ <table>
2394
+ <tbody>
2395
+ {result?.map(item => (
2396
+ <tr key={item.id}>
2397
+ <td>{item.name}</td>
2398
+ </tr>
2399
+ ))}
2400
+ </tbody>
2401
+ </table>
2402
+ )}
978
2403
  </div>
979
2404
  );
980
- };
2405
+ }
2406
+
2407
+ // 防抖工具
2408
+ function debounce<T extends (...args: any[]) => any>(
2409
+ func: T,
2410
+ wait: number
2411
+ ): (...args: Parameters<T>) => void {
2412
+ let timeout: NodeJS.Timeout;
2413
+ return (...args: Parameters<T>) => {
2414
+ clearTimeout(timeout);
2415
+ timeout = setTimeout(() => func(...args), wait);
2416
+ };
2417
+ }
981
2418
  ```
982
2419
 
983
- ### 流查询 Hooks
2420
+ ### 真实世界集成示例
984
2421
 
985
- #### useListStreamQuery Hook
2422
+ 显示与流行库集成的完整示例:
986
2423
 
987
- `useListStreamQuery` hook 管理列表流查询,返回服务器发送事件的 readable stream。
2424
+ #### React Query (TanStack Query) 集成
988
2425
 
989
2426
  ```typescript jsx
990
- import { useListStreamQuery } from '@ahoo-wang/fetcher-react';
2427
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
2428
+ import { useFetcher } from '@ahoo-wang/fetcher-react';
991
2429
 
992
- const MyComponent = () => {
993
- const { result, loading, error, execute, setCondition } = useListStreamQuery({
994
- initialQuery: { condition: {}, projection: {}, sort: [], limit: 100 },
995
- execute: async (listQuery) => {
996
- // 您的流获取逻辑
997
- return fetchListStream(listQuery);
998
- },
2430
+ function useUserData(userId: string) {
2431
+ return useQuery({
2432
+ queryKey: ['user', userId],
2433
+ queryFn: async () => {
2434
+ const { execute } = useFetcher();
2435
+ const result = await execute({
2436
+ url: `/api/users/${userId}`,
2437
+ method: 'GET'
2438
+ });
2439
+ return result;
2440
+ }
999
2441
  });
2442
+ }
2443
+
2444
+ function UserProfile({ userId }: { userId: string }) {
2445
+ const { data, isLoading, error } = useUserData(userId);
2446
+
2447
+ if (isLoading) return <div>加载中...</div>;
2448
+ if (error) return <div>错误: {error.message}</div>;
2449
+
2450
+ return <div>欢迎, {data.name}!</div>;
2451
+ }
2452
+ ```
2453
+
2454
+ #### 与 Redux Toolkit 集成
2455
+
2456
+ ```typescript jsx
2457
+ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
2458
+ import { useFetcher } from '@ahoo-wang/fetcher-react';
2459
+
2460
+ const fetchUserData = createAsyncThunk(
2461
+ 'user/fetchData',
2462
+ async (userId: string) => {
2463
+ const { execute } = useFetcher();
2464
+ return await execute({
2465
+ url: `/api/users/${userId}`,
2466
+ method: 'GET'
2467
+ });
2468
+ }
2469
+ );
2470
+
2471
+ const userSlice = createSlice({
2472
+ name: 'user',
2473
+ initialState: { data: null, loading: false, error: null },
2474
+ reducers: {},
2475
+ extraReducers: (builder) => {
2476
+ builder
2477
+ .addCase(fetchUserData.pending, (state) => {
2478
+ state.loading = true;
2479
+ })
2480
+ .addCase(fetchUserData.fulfilled, (state, action) => {
2481
+ state.loading = false;
2482
+ state.data = action.payload;
2483
+ })
2484
+ .addCase(fetchUserData.rejected, (state, action) => {
2485
+ state.loading = false;
2486
+ state.error = action.error.message;
2487
+ });
2488
+ }
2489
+ });
2490
+
2491
+ function UserComponent({ userId }: { userId: string }) {
2492
+ const dispatch = useDispatch();
2493
+ const { data, loading, error } = useSelector((state) => state.user);
1000
2494
 
1001
2495
  useEffect(() => {
1002
- if (result) {
1003
- const reader = result.getReader();
1004
- const readStream = async () => {
1005
- try {
1006
- while (true) {
1007
- const { done, value } = await reader.read();
1008
- if (done) break;
1009
- console.log('接收到:', value);
1010
- // 处理流事件
1011
- }
1012
- } catch (error) {
1013
- console.error('流错误:', error);
1014
- }
1015
- };
1016
- readStream();
2496
+ dispatch(fetchUserData(userId));
2497
+ }, [userId, dispatch]);
2498
+
2499
+ if (loading) return <div>加载中...</div>;
2500
+ if (error) return <div>错误: {error}</div>;
2501
+
2502
+ return <div>{data?.name}</div>;
2503
+ }
2504
+ ```
2505
+
2506
+ #### Zustand 集成
2507
+
2508
+ ```typescript jsx
2509
+ import { create } from 'zustand';
2510
+ import { useFetcher } from '@ahoo-wang/fetcher-react';
2511
+
2512
+ interface UserStore {
2513
+ user: any;
2514
+ loading: boolean;
2515
+ error: string | null;
2516
+ fetchUser: (userId: string) => Promise<void>;
2517
+ }
2518
+
2519
+ const useUserStore = create<UserStore>((set) => ({
2520
+ user: null,
2521
+ loading: false,
2522
+ error: null,
2523
+ fetchUser: async (userId) => {
2524
+ set({ loading: true, error: null });
2525
+ try {
2526
+ const { execute } = useFetcher();
2527
+ const user = await execute({
2528
+ url: `/api/users/${userId}`,
2529
+ method: 'GET'
2530
+ });
2531
+ set({ user, loading: false });
2532
+ } catch (error) {
2533
+ set({ error: error.message, loading: false });
1017
2534
  }
1018
- }, [result]);
2535
+ }
2536
+ }));
2537
+
2538
+ function UserComponent({ userId }: { userId: string }) {
2539
+ const { user, loading, error, fetchUser } = useUserStore();
2540
+
2541
+ useEffect(() => {
2542
+ fetchUser(userId);
2543
+ }, [userId, fetchUser]);
1019
2544
 
1020
2545
  if (loading) return <div>加载中...</div>;
1021
- if (error) return <div>错误: {error.message}</div>;
2546
+ if (error) return <div>错误: {error}</div>;
1022
2547
 
1023
- return (
1024
- <div>
1025
- <button onClick={execute}>开始流</button>
1026
- </div>
1027
- );
1028
- };
2548
+ return <div>{user?.name}</div>;
2549
+ }
1029
2550
  ```
1030
2551
 
1031
- ## 最佳实践
2552
+ ### 测试模式
1032
2553
 
1033
- ### 性能优化
2554
+ Hooks 的综合测试示例:
1034
2555
 
1035
- - 谨慎使用 `autoExecute: true`,避免在挂载时进行不必要的请求
1036
- - 当启用 `autoExecute` 时,使用 `setQuery` 更新查询以触发自动重新执行
1037
- - `execute` 函数中记忆化昂贵的计算
2556
+ ```typescript jsx
2557
+ import { renderHook, act, waitFor } from '@testing-library/react';
2558
+ import { useFetcher, useListQuery } from '@ahoo-wang/fetcher-react';
1038
2559
 
1039
- ### 错误处理
2560
+ // 模拟 fetcher
2561
+ jest.mock('@ahoo-wang/fetcher', () => ({
2562
+ Fetcher: jest.fn().mockImplementation(() => ({
2563
+ request: jest.fn(),
2564
+ })),
2565
+ }));
1040
2566
 
1041
- - 始终在组件中处理加载和错误状态
1042
- - 使用自定义错误类型以更好地分类错误
1043
- - 为瞬时故障实现重试逻辑
2567
+ describe('useFetcher', () => {
2568
+ it('应处理成功的获取', async () => {
2569
+ const mockData = { id: 1, name: 'Test' };
2570
+ const mockFetcher = { request: jest.fn().mockResolvedValue(mockData) };
1044
2571
 
1045
- ### 类型安全
2572
+ const { result } = renderHook(() => useFetcher({ fetcher: mockFetcher }));
1046
2573
 
1047
- - 为查询参数和结果定义严格的接口
1048
- - 在整个应用程序中一致使用泛型类型
1049
- - 启用严格 TypeScript 模式以获得最大安全性
2574
+ act(() => {
2575
+ result.current.execute({ url: '/api/test', method: 'GET' });
2576
+ });
1050
2577
 
1051
- ### 状态管理
2578
+ await waitFor(() => {
2579
+ expect(result.current.loading).toBe(false);
2580
+ expect(result.current.result).toEqual(mockData);
2581
+ expect(result.current.error).toBe(null);
2582
+ });
2583
+ });
1052
2584
 
1053
- - 与全局状态管理结合使用(Redux、Zustand)以处理复杂应用
1054
- - 使用 `useKeyStorage` 进行持久化的客户端数据存储
1055
- - 实现乐观更新以改善用户体验
2585
+ it('应处理获取错误', async () => {
2586
+ const mockError = new Error('网络错误');
2587
+ const mockFetcher = { request: jest.fn().mockRejectedValue(mockError) };
2588
+
2589
+ const { result } = renderHook(() => useFetcher({ fetcher: mockFetcher }));
2590
+
2591
+ act(() => {
2592
+ result.current.execute({ url: '/api/test', method: 'GET' });
2593
+ });
2594
+
2595
+ await waitFor(() => {
2596
+ expect(result.current.loading).toBe(false);
2597
+ expect(result.current.error).toEqual(mockError);
2598
+ expect(result.current.result).toBe(null);
2599
+ });
2600
+ });
2601
+ });
2602
+
2603
+ describe('useListQuery', () => {
2604
+ it('应管理查询状态', async () => {
2605
+ const mockData = [{ id: 1, name: 'Item 1' }];
2606
+ const mockExecute = jest.fn().mockResolvedValue(mockData);
2607
+
2608
+ const { result } = renderHook(() =>
2609
+ useListQuery({
2610
+ initialQuery: { condition: {}, projection: {}, sort: [], limit: 10 },
2611
+ execute: mockExecute,
2612
+ }),
2613
+ );
2614
+
2615
+ act(() => {
2616
+ result.current.execute();
2617
+ });
2618
+
2619
+ await waitFor(() => {
2620
+ expect(result.current.loading).toBe(false);
2621
+ expect(result.current.result).toEqual(mockData);
2622
+ });
2623
+
2624
+ expect(mockExecute).toHaveBeenCalledWith({
2625
+ condition: {},
2626
+ projection: {},
2627
+ sort: [],
2628
+ limit: 10,
2629
+ });
2630
+ });
2631
+
2632
+ it('应更新条件', () => {
2633
+ const { result } = renderHook(() =>
2634
+ useListQuery({
2635
+ initialQuery: { condition: {}, projection: {}, sort: [], limit: 10 },
2636
+ execute: jest.fn(),
2637
+ }),
2638
+ );
2639
+
2640
+ act(() => {
2641
+ result.current.setCondition({ status: 'active' });
2642
+ });
2643
+
2644
+ expect(result.current.condition).toEqual({ status: 'active' });
2645
+ });
2646
+ });
2647
+ ```
1056
2648
 
1057
2649
  ## API 参考
1058
2650
 
@@ -1067,7 +2659,7 @@ function useDebouncedCallback<T extends (...args: any[]) => any>(
1067
2659
  ): UseDebouncedCallbackReturn<T>;
1068
2660
  ```
1069
2661
 
1070
- 一个 React hook,为回调函数提供防抖版本,支持前缘/后缘执行选项。
2662
+ 为回调函数提供防抖版本的 React hook,支持前缘/后缘执行选项。
1071
2663
 
1072
2664
  **类型参数:**
1073
2665
 
@@ -1146,104 +2738,26 @@ function useDebouncedFetcher<R, E = FetcherError>(
1146
2738
 
1147
2739
  **返回:**
1148
2740
 
1149
- 包含以下内容的对象:
1150
-
1151
- - `loading`: 布尔值,表示获取当前是否正在执行
1152
- - `result`: 获取的解析值
1153
- - `error`: 执行期间发生的任何错误
1154
- - `status`: 当前执行状态
1155
- - `exchange`: 表示正在进行的获取操作的 FetchExchange 对象
1156
- - `run`: 使用请求参数执行防抖获取的函数
1157
- - `cancel`: 取消任何待处理防抖执行的函数
1158
- - `isPending`: 布尔值,表示防抖调用是否待处理
1159
-
1160
- #### useDebouncedFetcherQuery
1161
-
1162
- ```typescript
1163
- function useDebouncedFetcherQuery<Q, R, E = FetcherError>(
1164
- options: UseDebouncedFetcherQueryOptions<Q, R, E>,
1165
- ): UseDebouncedFetcherQueryReturn<Q, R, E>;
1166
- ```
1167
-
1168
- 将基于查询的 HTTP 获取与防抖相结合,非常适合搜索输入和动态查询场景,您希望根据查询参数防抖 API 调用。
1169
-
1170
- ```typescript jsx
1171
- import { useDebouncedFetcherQuery } from '@ahoo-wang/fetcher-react';
1172
-
1173
- interface SearchQuery {
1174
- keyword: string;
1175
- limit: number;
1176
- filters?: { category?: string };
1177
- }
1178
-
1179
- interface SearchResult {
1180
- items: Array<{ id: string; title: string }>;
1181
- total: number;
1182
- }
1183
-
1184
- const SearchComponent = () => {
1185
- const {
1186
- loading,
1187
- result,
1188
- error,
1189
- run,
1190
- cancel,
1191
- isPending,
1192
- setQuery,
1193
- getQuery,
1194
- } = useDebouncedFetcherQuery<SearchQuery, SearchResult>({
1195
- url: '/api/search',
1196
- initialQuery: { keyword: '', limit: 10 },
1197
- debounce: { delay: 300 }, // 防抖 300ms
1198
- autoExecute: false, // 挂载时不执行
1199
- });
1200
-
1201
- const handleSearch = (keyword: string) => {
1202
- setQuery({ keyword, limit: 10 }); // 如果 autoExecute 为 true,这将触发防抖执行
1203
- };
1204
-
1205
- const handleManualSearch = () => {
1206
- run(); // 使用当前查询手动防抖执行
1207
- };
2741
+ 包含以下内容的对象:
1208
2742
 
1209
- const handleCancel = () => {
1210
- cancel(); // 取消任何待处理的防抖执行
1211
- };
2743
+ - `loading`: 布尔值,表示获取当前是否正在执行
2744
+ - `result`: 获取的解析值
2745
+ - `error`: 执行期间发生的任何错误
2746
+ - `status`: 当前执行状态
2747
+ - `exchange`: 表示正在进行的获取操作的 FetchExchange 对象
2748
+ - `run`: 使用请求参数执行防抖获取的函数
2749
+ - `cancel`: 取消任何待处理防抖执行的函数
2750
+ - `isPending`: 布尔值,表示防抖调用是否待处理
1212
2751
 
1213
- if (loading) return <div>搜索中...</div>;
1214
- if (error) return <div>错误: {error.message}</div>;
2752
+ #### useDebouncedFetcherQuery
1215
2753
 
1216
- return (
1217
- <div>
1218
- <input
1219
- type="text"
1220
- onChange={(e) => handleSearch(e.target.value)}
1221
- placeholder="搜索..."
1222
- />
1223
- <button onClick={handleManualSearch} disabled={isPending()}>
1224
- {isPending() ? '搜索中...' : '搜索'}
1225
- </button>
1226
- <button onClick={handleCancel}>取消</button>
1227
- {result && (
1228
- <div>
1229
- 找到 {result.total} 项:
1230
- {result.items.map(item => (
1231
- <div key={item.id}>{item.title}</div>
1232
- ))}
1233
- </div>
1234
- )}
1235
- </div>
1236
- );
1237
- };
2754
+ ```typescript
2755
+ function useDebouncedFetcherQuery<Q, R, E = FetcherError>(
2756
+ options: UseDebouncedFetcherQueryOptions<Q, R, E>,
2757
+ ): UseDebouncedFetcherQueryReturn<Q, R, E>;
1238
2758
  ```
1239
2759
 
1240
- **主要特性:**
1241
-
1242
- - **查询状态管理**: 使用 `setQuery` 和 `getQuery` 自动查询参数处理
1243
- - **防抖执行**: 在快速用户输入期间防止过多 API 调用
1244
- - **自动执行**: 可选的在查询参数更改时自动执行
1245
- - **手动控制**: `run()` 用于手动执行,`cancel()` 用于取消
1246
- - **待处理状态**: `isPending()` 检查防抖调用是否排队
2760
+ 将基于查询的 HTTP 获取与防抖相结合,非常适合搜索输入和动态查询场景,您希望根据查询参数防抖 API 调用。
1247
2761
 
1248
2762
  **类型参数:**
1249
2763
 
@@ -1278,93 +2792,13 @@ const SearchComponent = () => {
1278
2792
 
1279
2793
  #### useDebouncedQuery
1280
2794
 
1281
- 将通用查询执行与防抖相结合,非常适合自定义查询操作,您希望根据查询参数防抖执行。
1282
-
1283
- ```typescript jsx
1284
- import { useDebouncedQuery } from '@ahoo-wang/fetcher-react';
1285
-
1286
- interface SearchQuery {
1287
- keyword: string;
1288
- limit: number;
1289
- filters?: { category?: string };
1290
- }
1291
-
1292
- interface SearchResult {
1293
- items: Array<{ id: string; title: string }>;
1294
- total: number;
1295
- }
1296
-
1297
- const SearchComponent = () => {
1298
- const {
1299
- loading,
1300
- result,
1301
- error,
1302
- run,
1303
- cancel,
1304
- isPending,
1305
- setQuery,
1306
- getQuery,
1307
- } = useDebouncedQuery<SearchQuery, SearchResult>({
1308
- initialQuery: { keyword: '', limit: 10 },
1309
- execute: async (query) => {
1310
- const response = await fetch('/api/search', {
1311
- method: 'POST',
1312
- body: JSON.stringify(query),
1313
- headers: { 'Content-Type': 'application/json' },
1314
- });
1315
- return response.json();
1316
- },
1317
- debounce: { delay: 300 }, // 防抖 300ms
1318
- autoExecute: false, // 挂载时不执行
1319
- });
1320
-
1321
- const handleSearch = (keyword: string) => {
1322
- setQuery({ keyword, limit: 10 }); // 如果 autoExecute 为 true,这将触发防抖执行
1323
- };
1324
-
1325
- const handleManualSearch = () => {
1326
- run(); // 使用当前查询手动防抖执行
1327
- };
1328
-
1329
- const handleCancel = () => {
1330
- cancel(); // 取消任何待处理的防抖执行
1331
- };
1332
-
1333
- if (loading) return <div>搜索中...</div>;
1334
- if (error) return <div>错误: {error.message}</div>;
1335
-
1336
- return (
1337
- <div>
1338
- <input
1339
- type="text"
1340
- onChange={(e) => handleSearch(e.target.value)}
1341
- placeholder="搜索..."
1342
- />
1343
- <button onClick={handleManualSearch} disabled={isPending()}>
1344
- {isPending() ? '搜索中...' : '搜索'}
1345
- </button>
1346
- <button onClick={handleCancel}>取消</button>
1347
- {result && (
1348
- <div>
1349
- 找到 {result.total} 项:
1350
- {result.items.map(item => (
1351
- <div key={item.id}>{item.title}</div>
1352
- ))}
1353
- </div>
1354
- )}
1355
- </div>
1356
- );
1357
- };
2795
+ ```typescript
2796
+ function useDebouncedQuery<Q, R, E = FetcherError>(
2797
+ options: UseDebouncedQueryOptions<Q, R, E>,
2798
+ ): UseDebouncedQueryReturn<Q, R, E>;
1358
2799
  ```
1359
2800
 
1360
- **主要特性:**
1361
-
1362
- - **查询状态管理**: 使用 `setQuery` 和 `getQuery` 自动查询参数处理
1363
- - **防抖执行**: 在快速查询更改期间防止过多操作
1364
- - **自动执行**: 可选的在查询参数更改时自动执行
1365
- - **手动控制**: `run()` 用于手动执行,`cancel()` 用于取消
1366
- - **待处理状态**: `isPending()` 检查防抖调用是否排队
1367
- - **自定义执行**: 灵活的 execute 函数用于任何查询操作
2801
+ 将通用查询执行与防抖相结合,非常适合自定义查询操作,您希望根据查询参数防抖执行。
1368
2802
 
1369
2803
  **类型参数:**
1370
2804
 
@@ -1499,9 +2933,7 @@ function usePromiseState<R = unknown, E = unknown>(
1499
2933
  - `setError`: 设置状态为 ERROR 并提供错误
1500
2934
  - `setIdle`: 设置状态为 IDLE
1501
2935
 
1502
- ### 工具 Hooks
1503
-
1504
- #### useRequestId
2936
+ ### useRequestId
1505
2937
 
1506
2938
  ```typescript
1507
2939
  function useRequestId(): UseRequestIdReturn;
@@ -1572,68 +3004,170 @@ React hook,用于使用 Map-like 接口管理多个 refs,允许通过键动
1572
3004
  - `RefKey = string | number | symbol`
1573
3005
  - `UseRefsReturn<T> extends Iterable<[RefKey, T]>`
1574
3006
 
1575
- ### 事件 Hooks
3007
+ ### useQuery
1576
3008
 
1577
- #### useEventSubscription Hook
3009
+ ```typescript
3010
+ function useQuery<Q, R, E = FetcherError>(
3011
+ options: UseQueryOptions<Q, R, E>,
3012
+ ): UseQueryReturn<Q, R, E>;
3013
+ ```
1578
3014
 
1579
- `useEventSubscription` hook 为类型化事件总线提供了 React 接口。它自动管理订阅生命周期,同时提供手动控制功能以增加灵活性。
3015
+ 用于管理基于查询的异步操作的 React hook,具有自动状态管理和执行控制。
1580
3016
 
1581
- ```typescript jsx
1582
- import { useEventSubscription } from '@ahoo-wang/fetcher-react';
1583
- import { eventBus } from './eventBus';
3017
+ **类型参数:**
1584
3018
 
1585
- function MyComponent() {
1586
- const { subscribe, unsubscribe } = useEventSubscription({
1587
- bus: eventBus,
1588
- handler: {
1589
- name: 'myEvent',
1590
- handle: (event) => {
1591
- console.log('收到事件:', event);
1592
- }
1593
- }
1594
- });
3019
+ - `Q`: 查询参数的类型
3020
+ - `R`: 结果值的类型
3021
+ - `E`: 错误值的类型(默认为 FetcherError)
1595
3022
 
1596
- // hook 在组件挂载时自动订阅,在卸载时自动取消订阅
1597
- // 如需要,您也可以手动控制订阅
1598
- const handleToggleSubscription = () => {
1599
- if (someCondition) {
1600
- subscribe();
1601
- } else {
1602
- unsubscribe();
1603
- }
1604
- };
3023
+ **参数:**
1605
3024
 
1606
- return <div>我的组件</div>;
1607
- }
3025
+ - `options`: 查询的配置选项
3026
+ - `initialQuery`: 初始查询参数
3027
+ - `execute`: 使用给定参数和可选属性执行查询的函数
3028
+ - `autoExecute?`: 是否在挂载和查询更改时自动执行查询
3029
+ - 所有来自 `UseExecutePromiseOptions` 的选项
3030
+
3031
+ **返回值:**
3032
+
3033
+ 包含查询状态和控制函数的对象:
3034
+
3035
+ - `loading`: 布尔值,指示查询当前是否正在执行
3036
+ - `result`: 查询的解析值
3037
+ - `error`: 执行期间发生的任何错误
3038
+ - `status`: 当前执行状态
3039
+ - `execute`: 使用当前参数执行查询的函数
3040
+ - `reset`: 重置 Promise 状态的函数
3041
+ - `abort`: 中止当前操作的函数
3042
+ - `getQuery`: 检索当前查询参数的函数
3043
+ - `setQuery`: 更新查询参数的函数
3044
+
3045
+ ### useQueryState
3046
+
3047
+ ```typescript
3048
+ function useQueryState<Q>(
3049
+ options: UseQueryStateOptions<Q>,
3050
+ ): UseQueryStateReturn<Q>;
1608
3051
  ```
1609
3052
 
1610
- 关键特性:
3053
+ 用于管理查询状态的 React hook,具有自动执行功能。
1611
3054
 
1612
- - **自动生命周期管理**: 在组件挂载时自动订阅,在卸载时自动取消订阅
1613
- - **手动控制**: 提供 `subscribe` 和 `unsubscribe` 函数以进行额外控制
1614
- - **类型安全**: 完全支持 TypeScript,具有泛型事件类型
1615
- - **错误处理**: 对失败的订阅尝试记录警告
1616
- - **事件总线集成**: 与 `@ahoo-wang/fetcher-eventbus` TypedEventBus 实例无缝配合
3055
+ **类型参数:**
1617
3056
 
1618
- ### 存储 Hooks
3057
+ - `Q`: 查询参数的类型
1619
3058
 
1620
- #### useKeyStorage
3059
+ **参数:**
1621
3060
 
1622
- ```typescript jsx
3061
+ - `options`: hook 的配置选项
3062
+ - `initialQuery`: 要存储和管理的初始查询参数
3063
+ - `autoExecute?`: 是否在查询更改时或组件挂载时自动执行
3064
+ - `execute`: 使用当前查询参数执行的函数
3065
+
3066
+ **返回值:**
3067
+
3068
+ 包含以下内容的对象:
3069
+
3070
+ - `getQuery`: 检索当前查询参数的函数
3071
+ - `setQuery`: 更新查询参数的函数。如果 autoExecute 为 true 会触发执行
3072
+
3073
+ ### useMounted
3074
+
3075
+ ```typescript
3076
+ function useMounted(): () => boolean;
3077
+ ```
3078
+
3079
+ 返回检查组件是否仍挂载的函数的 React hook。
3080
+
3081
+ **返回值:**
3082
+
3083
+ 当组件仍挂载时返回 `true`,否则返回 `false` 的函数。
3084
+
3085
+ ### useForceUpdate
3086
+
3087
+ ```typescript
3088
+ function useForceUpdate(): () => void;
3089
+ ```
3090
+
3091
+ 返回强制组件重新渲染的函数的 React hook。
3092
+
3093
+ **返回值:**
3094
+
3095
+ 调用时强制组件重新渲染的函数。
3096
+
3097
+ ### useEventSubscription
3098
+
3099
+ ```typescript
3100
+ function useEventSubscription<EVENT = unknown>(
3101
+ options: UseEventSubscriptionOptions<EVENT>,
3102
+ ): UseEventSubscriptionReturn;
3103
+ ```
3104
+
3105
+ 为类型化事件总线提供 React 接口的 hook。自动管理订阅生命周期,同时提供手动控制功能。
3106
+
3107
+ **类型参数:**
3108
+
3109
+ - `EVENT`: 事件总线处理的事件类型(默认为 unknown)
3110
+
3111
+ **参数:**
3112
+
3113
+ - `options`: 订阅的配置选项
3114
+ - `bus`: 要订阅的 TypedEventBus 实例
3115
+ - `handler`: 具有名称和处理方法的事件处理函数
3116
+
3117
+ **返回值:**
3118
+
3119
+ 包含以下内容的对象:
3120
+
3121
+ - `subscribe`: 手动订阅事件总线的函数(返回布尔值成功状态)
3122
+ - `unsubscribe`: 手动取消订阅事件总线的函数(返回布尔值成功状态)
3123
+
3124
+ **相关类型:**
3125
+
3126
+ - `UseEventSubscriptionOptions<EVENT>`: 具有 bus 和 handler 属性的配置接口
3127
+ - `UseEventSubscriptionReturn`: 具有 subscribe 和 unsubscribe 方法的返回接口
3128
+
3129
+ ### useKeyStorage
3130
+
3131
+ ```typescript
3132
+ // 不使用默认值 - 可能返回 null
1623
3133
  function useKeyStorage<T>(
1624
3134
  keyStorage: KeyStorage<T>,
1625
3135
  ): [T | null, (value: T) => void];
3136
+
3137
+ // 使用默认值 - 保证非空
3138
+ function useKeyStorage<T>(
3139
+ keyStorage: KeyStorage<T>,
3140
+ defaultValue: T,
3141
+ ): [T, (value: T) => void];
1626
3142
  ```
1627
3143
 
1628
- 为 KeyStorage 实例提供状态管理的 React hook
3144
+ 为 KeyStorage 实例提供响应式状态管理的 React hook。订阅存储更改并返回当前值以及设置器函数。可选择接受默认值以在存储为空时使用。
3145
+
3146
+ **类型参数:**
3147
+
3148
+ - `T`: 存储在键存储中的值的类型
1629
3149
 
1630
3150
  **参数:**
1631
3151
 
1632
- - `keyStorage`: 要订阅和管理的 KeyStorage 实例
3152
+ - `keyStorage`: 要订阅和管理的 KeyStorage 实例。应该是稳定引用(useRef、memo 或模块级实例)
3153
+ - `defaultValue` _(可选)_: 当存储为空时使用的默认值。提供时,hook 保证返回的值永远不会为 null
1633
3154
 
1634
3155
  **返回值:**
1635
3156
 
1636
- - 包含当前存储值和更新函数的元组
3157
+ - **不使用默认值**: `[T | null, (value: T) => void]` - 元组,其中第一个元素在存储为空时可能为 null
3158
+ - **使用默认值**: `[T, (value: T) => void]` - 元组,其中第一个元素保证不为 null(存储的值或默认值)
3159
+
3160
+ **示例:**
3161
+
3162
+ ```typescript
3163
+ // 不使用默认值
3164
+ const [value, setValue] = useKeyStorage(keyStorage);
3165
+ // value: string | null
3166
+
3167
+ // 使用默认值
3168
+ const [theme, setTheme] = useKeyStorage(themeStorage, 'light');
3169
+ // theme: string (永不为 null)
3170
+ ```
1637
3171
 
1638
3172
  ### useImmerKeyStorage
1639
3173
 
@@ -1658,11 +3192,11 @@ function useImmerKeyStorage<T>(
1658
3192
 
1659
3193
  **类型参数:**
1660
3194
 
1661
- - `T`: 存储值的数据类型
3195
+ - `T`: 存储在键存储中的值的类型
1662
3196
 
1663
3197
  **参数:**
1664
3198
 
1665
- - `keyStorage`: 要订阅和管理的 KeyStorage 实例。应该是稳定的引用(useRef、memo 或模块级实例)
3199
+ - `keyStorage`: 要订阅和管理的 KeyStorage 实例。应该是稳定引用(useRef、memo 或模块级实例)
1666
3200
  - `defaultValue` _(可选)_: 当存储为空时使用的默认值。提供时,hook 保证返回的值永远不会为 null
1667
3201
 
1668
3202
  **返回值:**
@@ -1721,7 +3255,7 @@ function useListQuery<R, FIELDS extends string = string, E = FetcherError>(
1721
3255
  **参数:**
1722
3256
 
1723
3257
  - `options`: 包含 initialQuery 和 list 函数的配置选项
1724
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3258
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1725
3259
 
1726
3260
  **返回值:**
1727
3261
 
@@ -1746,7 +3280,7 @@ function usePagedQuery<R, FIELDS extends string = string, E = unknown>(
1746
3280
  **参数:**
1747
3281
 
1748
3282
  - `options`: 包含 initialQuery 和 query 函数的配置选项
1749
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3283
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1750
3284
 
1751
3285
  **返回值:**
1752
3286
 
@@ -1771,7 +3305,7 @@ function useSingleQuery<R, FIELDS extends string = string, E = unknown>(
1771
3305
  **参数:**
1772
3306
 
1773
3307
  - `options`: 包含 initialQuery 和 query 函数的配置选项
1774
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3308
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1775
3309
 
1776
3310
  **返回值:**
1777
3311
 
@@ -1795,7 +3329,7 @@ function useCountQuery<FIELDS extends string = string, E = FetcherError>(
1795
3329
  **参数:**
1796
3330
 
1797
3331
  - `options`: 包含 initialQuery 和 execute 函数的配置选项
1798
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3332
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1799
3333
 
1800
3334
  **返回值:**
1801
3335
 
@@ -1821,7 +3355,7 @@ function useFetcherCountQuery<FIELDS extends string = string, E = FetcherError>(
1821
3355
  - `options`: 计数查询的配置选项,包括条件、fetcher 实例和其他查询设置
1822
3356
  - `url`: 从中获取计数的 URL
1823
3357
  - `initialQuery`: 计数查询的初始条件
1824
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3358
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1825
3359
 
1826
3360
  **返回值:**
1827
3361
 
@@ -1852,7 +3386,7 @@ function useFetcherPagedQuery<
1852
3386
  - `options`: 分页查询的配置选项,包括分页查询参数、fetcher 实例和其他查询设置
1853
3387
  - `url`: 从中获取分页数据的 URL
1854
3388
  - `initialQuery`: 初始分页查询配置
1855
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3389
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1856
3390
 
1857
3391
  **返回值:**
1858
3392
 
@@ -1883,7 +3417,7 @@ function useFetcherListQuery<
1883
3417
  - `options`: 列表查询的配置选项,包括列表查询参数、fetcher 实例和其他查询设置
1884
3418
  - `url`: 从中获取列表数据的 URL
1885
3419
  - `initialQuery`: 初始列表查询配置
1886
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3420
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1887
3421
 
1888
3422
  **返回值:**
1889
3423
 
@@ -1914,7 +3448,7 @@ function useFetcherListStreamQuery<
1914
3448
  - `options`: 列表流查询的配置选项,包括列表查询参数、fetcher 实例和其他查询设置
1915
3449
  - `url`: 从中获取流数据的 URL
1916
3450
  - `initialQuery`: 初始列表查询配置
1917
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3451
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1918
3452
 
1919
3453
  **返回值:**
1920
3454
 
@@ -1945,7 +3479,7 @@ function useFetcherSingleQuery<
1945
3479
  - `options`: 单个查询的配置选项,包括单个查询参数、fetcher 实例和其他查询设置
1946
3480
  - `url`: 从中获取单个项目的 URL
1947
3481
  - `initialQuery`: 初始单个查询配置
1948
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3482
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1949
3483
 
1950
3484
  **返回值:**
1951
3485
 
@@ -1974,7 +3508,7 @@ function useListStreamQuery<
1974
3508
  **参数:**
1975
3509
 
1976
3510
  - `options`: 包含 initialQuery 和 listStream 函数的配置选项
1977
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3511
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1978
3512
 
1979
3513
  **返回值:**
1980
3514