@ahoo-wang/fetcher-react 3.5.8 → 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 (75) hide show
  1. package/README.md +868 -609
  2. package/README.zh-CN.md +2079 -672
  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/useSecurity.d.ts +1 -1
  17. package/dist/fetcher/debounced/index.d.ts +3 -0
  18. package/dist/fetcher/debounced/index.d.ts.map +1 -0
  19. package/dist/fetcher/{useDebouncedFetcher.d.ts → debounced/useDebouncedFetcher.d.ts} +2 -2
  20. package/dist/fetcher/debounced/useDebouncedFetcher.d.ts.map +1 -0
  21. package/dist/{wow/debounce → fetcher/debounced}/useDebouncedFetcherQuery.d.ts +1 -1
  22. package/dist/fetcher/debounced/useDebouncedFetcherQuery.d.ts.map +1 -0
  23. package/dist/fetcher/index.d.ts +2 -1
  24. package/dist/fetcher/index.d.ts.map +1 -1
  25. package/dist/{wow → fetcher}/useFetcherQuery.d.ts +3 -3
  26. package/dist/fetcher/useFetcherQuery.d.ts.map +1 -0
  27. package/dist/index.es.js +308 -308
  28. package/dist/index.es.js.map +1 -1
  29. package/dist/index.umd.js +1 -1
  30. package/dist/index.umd.js.map +1 -1
  31. package/dist/types.d.ts +6 -0
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/wow/fetcher/index.d.ts +6 -0
  34. package/dist/wow/fetcher/index.d.ts.map +1 -0
  35. package/dist/wow/{useFetcherCountQuery.d.ts → fetcher/useFetcherCountQuery.d.ts} +2 -2
  36. package/dist/wow/fetcher/useFetcherCountQuery.d.ts.map +1 -0
  37. package/dist/wow/{useFetcherListQuery.d.ts → fetcher/useFetcherListQuery.d.ts} +2 -2
  38. package/dist/wow/fetcher/useFetcherListQuery.d.ts.map +1 -0
  39. package/dist/wow/{useFetcherListStreamQuery.d.ts → fetcher/useFetcherListStreamQuery.d.ts} +2 -2
  40. package/dist/wow/fetcher/useFetcherListStreamQuery.d.ts.map +1 -0
  41. package/dist/wow/{useFetcherPagedQuery.d.ts → fetcher/useFetcherPagedQuery.d.ts} +2 -2
  42. package/dist/wow/fetcher/useFetcherPagedQuery.d.ts.map +1 -0
  43. package/dist/wow/{useFetcherSingleQuery.d.ts → fetcher/useFetcherSingleQuery.d.ts} +2 -2
  44. package/dist/wow/fetcher/useFetcherSingleQuery.d.ts.map +1 -0
  45. package/dist/wow/index.d.ts +1 -10
  46. package/dist/wow/index.d.ts.map +1 -1
  47. package/dist/wow/useCountQuery.d.ts +1 -1
  48. package/dist/wow/useCountQuery.d.ts.map +1 -1
  49. package/dist/wow/useListQuery.d.ts +1 -1
  50. package/dist/wow/useListQuery.d.ts.map +1 -1
  51. package/dist/wow/useListStreamQuery.d.ts +1 -1
  52. package/dist/wow/useListStreamQuery.d.ts.map +1 -1
  53. package/dist/wow/usePagedQuery.d.ts +1 -1
  54. package/dist/wow/usePagedQuery.d.ts.map +1 -1
  55. package/dist/wow/useSingleQuery.d.ts +1 -1
  56. package/dist/wow/useSingleQuery.d.ts.map +1 -1
  57. package/package.json +1 -1
  58. package/dist/core/useDebouncedCallback.d.ts.map +0 -1
  59. package/dist/core/useDebouncedExecutePromise.d.ts.map +0 -1
  60. package/dist/fetcher/useDebouncedFetcher.d.ts.map +0 -1
  61. package/dist/wow/debounce/index.d.ts +0 -3
  62. package/dist/wow/debounce/index.d.ts.map +0 -1
  63. package/dist/wow/debounce/useDebouncedFetcherQuery.d.ts.map +0 -1
  64. package/dist/wow/debounce/useDebouncedQuery.d.ts.map +0 -1
  65. package/dist/wow/types.d.ts +0 -7
  66. package/dist/wow/types.d.ts.map +0 -1
  67. package/dist/wow/useFetcherCountQuery.d.ts.map +0 -1
  68. package/dist/wow/useFetcherListQuery.d.ts.map +0 -1
  69. package/dist/wow/useFetcherListStreamQuery.d.ts.map +0 -1
  70. package/dist/wow/useFetcherPagedQuery.d.ts.map +0 -1
  71. package/dist/wow/useFetcherQuery.d.ts.map +0 -1
  72. package/dist/wow/useFetcherSingleQuery.d.ts.map +0 -1
  73. package/dist/wow/useQuery.d.ts.map +0 -1
  74. package/dist/wow/useQueryState.d.ts.map +0 -1
  75. /package/dist/core/{useDebouncedCallback.d.ts → debounced/useDebouncedCallback.d.ts} +0 -0
package/README.zh-CN.md CHANGED
@@ -27,21 +27,27 @@
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)
@@ -49,20 +55,20 @@
49
55
  - [useSecurity](#usesecurity-hook)
50
56
  - [SecurityProvider](#securityprovider)
51
57
  - [useSecurityContext](#usesecuritycontext-hook)
58
+ - [RouteGuard](#routeguard)
52
59
  - [Wow 查询 Hooks](#wow-查询-hooks)
53
- - [基础查询 Hooks](#基础查询-hooks)
54
- - [useListQuery](#uselistquery-hook)
55
- - [usePagedQuery](#usepagedquery-hook)
56
- - [useSingleQuery](#usesinglequery-hook)
57
- - [useCountQuery](#usecountquery-hook)
58
- - [获取查询 Hooks](#获取查询-hooks)
59
- - [useFetcherListQuery](#usefetcherlistquery-hook)
60
- - [useFetcherPagedQuery](#usefetcherpagedquery-hook)
61
- - [useFetcherSingleQuery](#usefetchersinglequery-hook)
62
- - [useFetcherCountQuery](#usefetchercountquery-hook)
63
- - [流查询 Hooks](#流查询-hooks)
64
- - [useListStreamQuery](#useliststreamquery-hook)
65
- - [useFetcherListStreamQuery](#usefetcherliststreamquery-hook)
60
+ - [基础查询 Hooks](#基础查询-hooks)
61
+ - [useListQuery](#uselistquery-hook)
62
+ - [usePagedQuery](#usepagedquery-hook)
63
+ - [useSingleQuery](#usesinglequery-hook)
64
+ - [useCountQuery](#usecountquery-hook)
65
+ - [useListStreamQuery](#useliststreamquery-hook)
66
+ - [Fetcher 查询 Hooks](#fetcher-查询-hooks)
67
+ - [useFetcherListQuery](#usefetcherlistquery-hook)
68
+ - [useFetcherPagedQuery](#usefetcherpagedquery-hook)
69
+ - [useFetcherSingleQuery](#usefetchersinglequery-hook)
70
+ - [useFetcherCountQuery](#usefetchercountquery-hook)
71
+ - [useFetcherListStreamQuery](#usefetcherliststreamquery-hook)
66
72
  - [最佳实践](#最佳实践)
67
73
  - [API 参考](#api-参考)
68
74
  - [许可证](#许可证)
@@ -105,286 +111,1241 @@ function App() {
105
111
 
106
112
  ### 核心 Hooks
107
113
 
108
- #### useFetcher Hook
114
+ #### useExecutePromise
109
115
 
110
- `useFetcher` hook 提供完整的数据获取功能,具有自动状态管理、竞态条件保护和灵活的配置选项。
116
+ `useExecutePromise` hook 用于管理异步操作,具有自动状态处理、竞态条件保护和 Promise 状态选项。它包含用于取消操作的自动 AbortController 支持。
111
117
 
112
118
  ```typescript jsx
113
- import { useFetcher } from '@ahoo-wang/fetcher-react';
119
+ import { useExecutePromise } from '@ahoo-wang/fetcher-react';
114
120
 
115
121
  const MyComponent = () => {
116
- 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
+ };
117
132
 
118
133
  const handleFetch = () => {
119
- execute({ url: '/api/users', method: 'GET' });
120
- };
121
- ```
134
+ execute(fetchData); // 使用 Promise 供应商
135
+ };
122
136
 
123
- ### useImmerKeyStorage Hook
137
+ const handleDirectPromise = () => {
138
+ const promise = fetch('/api/data').then(res => res.text());
139
+ execute(promise); // 使用直接 Promise
140
+ };
124
141
 
125
- 🚀 **Immer 驱动的不可变状态管理** - `useImmerKeyStorage` hook 通过集成 Immer 的 `produce` 函数扩展了 `useKeyStorage`,允许开发者以直观的"可变"方式更新存储值,同时在底层保持不可变性。非常适合复杂对象的操作,具有自动存储同步功能。
142
+ const handleAbort = () => {
143
+ abort(); // 手动中止当前操作
144
+ };
126
145
 
127
- #### 主要优势
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
+ ```
128
159
 
129
- - **直观的变更语法**: 编写看起来可变的代码,但产生不可变更新
130
- - **深度对象支持**: 轻松处理嵌套对象和数组
131
- - **类型安全**: 完整的 TypeScript 支持和编译时错误检查
132
- - **性能优化**: 利用 Immer 的结构共享和最小化重渲染
133
- - **自动同步**: 变更自动持久化到存储并跨组件同步
160
+ ##### 中止控制器支持
134
161
 
135
- #### 使用场景
162
+ hook 会为每个操作自动创建一个 AbortController,并提供管理取消的方法:
136
163
 
137
- 在需要以下情况时选择 `useImmerKeyStorage` 而不是 `useKeyStorage`:
164
+ - **自动清理**: 组件卸载时操作会自动中止
165
+ - **手动中止**: 使用 `abort()` 方法中止正在进行的操作
166
+ - **onAbort 回调**: 配置在操作中止时触发的回调(手动或自动)
167
+ - **AbortController 访问**: AbortController 会传递给 Promise 供应商以进行高级取消处理
138
168
 
139
- - 更新嵌套对象属性
140
- - 执行复杂的数组操作(push、splice 等)
141
- - 原子性地进行多个相关变更
142
- - 处理深度嵌套的数据结构
169
+ #### usePromiseState
170
+
171
+ `usePromiseState` hook 提供 Promise 操作的状态管理,无执行逻辑。支持静态选项和动态选项供应商。
143
172
 
144
173
  ```typescript jsx
145
- import { KeyStorage } from '@ahoo-wang/fetcher-storage';
146
- import { useImmerKeyStorage } from '@ahoo-wang/fetcher-react';
174
+ import { usePromiseState, PromiseStatus } from '@ahoo-wang/fetcher-react';
147
175
 
148
176
  const MyComponent = () => {
149
- const prefsStorage = new KeyStorage<{
150
- theme: string;
151
- volume: number;
152
- notifications: boolean;
153
- shortcuts: { [key: string]: string };
154
- }>({
155
- key: 'user-prefs'
156
- });
177
+ const { status, loading, result, error, setSuccess, setError, setIdle } = usePromiseState<string>();
157
178
 
158
- // 不使用默认值 - 可能为 null
159
- const [prefs, updatePrefs, clearPrefs] = useImmerKeyStorage(prefsStorage);
179
+ const handleSuccess = () => setSuccess('数据已加载');
180
+ const handleError = () => setError(new Error('加载失败'));
160
181
 
161
182
  return (
162
183
  <div>
163
- <p>主题: {prefs?.theme || '默认'}</p>
164
- <button onClick={() => updatePrefs(draft => { draft.theme = 'dark'; })}>
165
- 切换到深色主题
166
- </button>
167
- <button onClick={() => updatePrefs(draft => { draft.volume += 10; })}>
168
- 增加音量
169
- </button>
170
- <button onClick={clearPrefs}>
171
- 清除偏好设置
172
- </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>}
173
191
  </div>
174
192
  );
175
193
  };
176
194
  ```
177
195
 
178
- #### 使用默认值
196
+ ##### 使用选项供应商的 usePromiseState
179
197
 
180
198
  ```typescript jsx
181
- const AudioControls = () => {
182
- const settingsStorage = new KeyStorage<{ volume: number; muted: boolean }>({
183
- 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
+ },
184
213
  });
185
214
 
186
- // 使用默认值 - 保证不为 null
187
- const [settings, updateSettings, resetSettings] = useImmerKeyStorage(
188
- settingsStorage,
189
- { volume: 50, muted: false }
190
- );
215
+ const { setSuccess, setError } = usePromiseState<string>(optionsSupplier);
191
216
 
192
217
  return (
193
218
  <div>
194
- <p>音量: {settings.volume}%</p>
195
- <button onClick={() => updateSettings(draft => {
196
- draft.volume = Math.min(100, draft.volume + 10);
197
- draft.muted = false;
198
- })}>
199
- 增加音量
200
- </button>
201
- <button onClick={() => updateSettings(draft => { draft.muted = !draft.muted; })}>
202
- 切换静音
203
- </button>
204
- <button onClick={resetSettings}>
205
- 重置为默认值
206
- </button>
219
+ <button onClick={() => setSuccess('动态成功!')}>设置成功</button>
220
+ <button onClick={() => setError(new Error('动态错误!'))}>设置错误</button>
207
221
  </div>
208
222
  );
209
223
  };
210
224
  ```
211
225
 
212
- #### 高级用法模式
226
+ #### useRequestId
213
227
 
214
- ##### 批量更新
228
+ `useRequestId` hook 提供请求 ID 管理,用于防止异步操作中的竞态条件。
215
229
 
216
230
  ```typescript jsx
217
- const updateUserProfile = () => {
218
- updatePrefs(draft => {
219
- draft.theme = 'dark';
220
- draft.notifications = true;
221
- draft.volume = 75;
222
- });
223
- };
224
- ```
231
+ import { useRequestId } from '@ahoo-wang/fetcher-react';
225
232
 
226
- ##### 数组操作
227
-
228
- ```typescript jsx
229
- const todoStorage = new KeyStorage<{
230
- todos: Array<{ id: number; text: string; done: boolean }>;
231
- }>({
232
- key: 'todos',
233
- });
233
+ const MyComponent = () => {
234
+ const { generate, isLatest, invalidate } = useRequestId();
234
235
 
235
- const [state, updateState] = useImmerKeyStorage(todoStorage, { todos: [] });
236
+ const handleFetch = async () => {
237
+ const requestId = generate();
236
238
 
237
- // 添加新待办事项
238
- const addTodo = (text: string) => {
239
- updateState(draft => {
240
- draft.todos.push({
241
- id: Date.now(),
242
- text,
243
- done: false,
244
- });
245
- });
246
- };
239
+ try {
240
+ const result = await fetchData();
247
241
 
248
- // 切换待办事项状态
249
- const toggleTodo = (id: number) => {
250
- updateState(draft => {
251
- const todo = draft.todos.find(t => t.id === id);
252
- if (todo) {
253
- todo.done = !todo.done;
242
+ if (isLatest(requestId)) {
243
+ setData(result);
244
+ }
245
+ } catch (error) {
246
+ if (isLatest(requestId)) {
247
+ setError(error);
248
+ }
254
249
  }
255
- });
256
- };
250
+ };
257
251
 
258
- // 清除已完成的待办事项
259
- const clearCompleted = () => {
260
- updateState(draft => {
261
- draft.todos = draft.todos.filter(todo => !todo.done);
262
- });
252
+ return (
253
+ <div>
254
+ <button onClick={handleFetch}>获取数据</button>
255
+ <button onClick={invalidate}>取消进行中</button>
256
+ </div>
257
+ );
263
258
  };
264
259
  ```
265
260
 
266
- ##### 嵌套对象更新
261
+ #### useLatest
262
+
263
+ `useLatest` hook 返回包含最新值的 ref 对象,用于在异步回调中访问当前值。
267
264
 
268
265
  ```typescript jsx
269
- const configStorage = new KeyStorage<{
270
- ui: { theme: string; language: string };
271
- features: { [key: string]: boolean };
272
- }>({
273
- key: 'app-config',
274
- });
266
+ import { useLatest } from '@ahoo-wang/fetcher-react';
275
267
 
276
- const [config, updateConfig] = useImmerKeyStorage(configStorage, {
277
- ui: { theme: 'light', language: 'zh' },
278
- features: {},
279
- });
268
+ const MyComponent = () => {
269
+ const [count, setCount] = useState(0);
270
+ const latestCount = useLatest(count);
280
271
 
281
- // 更新嵌套属性
282
- const updateTheme = (theme: string) => {
283
- updateConfig(draft => {
284
- draft.ui.theme = theme;
285
- });
286
- };
272
+ const handleAsync = async () => {
273
+ await someAsyncOperation();
274
+ console.log('最新计数:', latestCount.current); // 始终是最新的
275
+ };
287
276
 
288
- const toggleFeature = (feature: string) => {
289
- updateConfig(draft => {
290
- draft.features[feature] = !draft.features[feature];
291
- });
277
+ return (
278
+ <div>
279
+ <p>计数: {count}</p>
280
+ <button onClick={() => setCount(c => c + 1)}>增加</button>
281
+ <button onClick={handleAsync}>异步日志</button>
282
+ </div>
283
+ );
292
284
  };
293
285
  ```
294
286
 
295
- ##### 带验证的条件更新
287
+ #### useRefs
288
+
289
+ `useRefs` hook 提供 Map-like 接口用于动态管理多个 React refs。它允许通过键注册、检索和管理 refs,并在组件卸载时自动清理。
296
290
 
297
291
  ```typescript jsx
298
- const updateVolume = (newVolume: number) => {
299
- updateSettings(draft => {
300
- if (newVolume >= 0 && newVolume <= 100) {
301
- draft.volume = newVolume;
302
- draft.muted = false; // 音量改变时取消静音
303
- }
304
- });
305
- };
306
- ```
292
+ import { useRefs } from '@ahoo-wang/fetcher-react';
307
293
 
308
- ##### 返回新值
294
+ const MyComponent = () => {
295
+ const refs = useRefs<HTMLDivElement>();
309
296
 
310
- ```typescript jsx
311
- // 替换整个状态
312
- const resetToFactorySettings = () => {
313
- updateSettings(() => ({ volume: 50, muted: false }));
314
- };
297
+ const handleFocus = (key: string) => {
298
+ const element = refs.get(key);
299
+ element?.focus();
300
+ };
315
301
 
316
- // 计算更新
317
- const setMaxVolume = () => {
318
- updateSettings(draft => ({ ...draft, volume: 100, muted: false }));
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
+ );
319
310
  };
320
311
  ```
321
312
 
322
- ##### 错误处理
313
+ 主要特性:
323
314
 
324
- ```typescript jsx
325
- const safeUpdate = (updater: (draft: any) => void) => {
326
- try {
327
- updatePrefs(updater);
328
- } catch (error) {
329
- console.error('更新偏好设置失败:', error);
330
- // 适当处理错误
331
- }
332
- };
333
- ```
315
+ - **动态注册**: 使用字符串、数字或符号键注册 refs
316
+ - **Map-like API**: 完整的 Map 接口,包括 get、set、has、delete
317
+ - **自动清理**: 组件卸载时清除 refs
318
+ - **类型安全**: 完整的 TypeScript 支持 ref 类型
334
319
 
335
- #### 最佳实践
320
+ #### useQuery
336
321
 
337
- ##### 推荐做法
322
+ `useQuery` hook 提供完整的查询基础异步操作管理解决方案,具有自动状态管理和执行控制。
338
323
 
339
- - 用于复杂对象更新和数组操作
340
- - 利用 Immer draft 变更编写可读代码
341
- - 在单个更新调用中组合多个相关变更
342
- - 对保证非空状态使用默认值
343
- - 在更新函数中适当处理错误
324
+ ```typescript jsx
325
+ import { useQuery } from '@ahoo-wang/fetcher-react';
344
326
 
345
- ##### 避免做法
327
+ interface UserQuery {
328
+ id: string;
329
+ }
346
330
 
347
- - 不要直接用赋值修改 draft 参数(`draft = newValue`)
348
- - 不要在更新函数中执行副作用
349
- - 不要依赖对象比较的引用相等性
350
- - 不要用于简单的原始值更新(应使用 `useKeyStorage`)
331
+ interface User {
332
+ id: string;
333
+ name: string;
334
+ }
351
335
 
352
- ##### 性能提示
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
+ });
353
345
 
354
- - 将相关更新批量处理以最小化存储操作
355
- - 当新状态依赖于之前状态时使用函数式更新
356
- - 如果更新函数经常重新创建,考虑使用 `useCallback`
357
- - 如果处理非常大的对象,请分析更新性能
346
+ const handleUserChange = (userId: string) => {
347
+ setQuery({ id: userId }); // 如果 autoExecute 为 true 会自动执行
348
+ };
358
349
 
359
- ##### TypeScript 集成
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
+ ```
360
360
 
361
- ```typescript jsx
362
- // 为更好的安全性定义严格类型
363
- type UserPreferences = {
364
- theme: 'light' | 'dark' | 'auto';
365
- volume: number; // 0-100
366
- notifications: boolean;
367
- shortcuts: Record<string, string>;
368
- };
361
+ #### useQueryState
369
362
 
370
- const prefsStorage = new KeyStorage<UserPreferences>({
371
- key: 'user-prefs',
372
- });
363
+ `useQueryState` hook 提供查询参数的状态管理,具有自动执行功能。
373
364
 
374
- // TypeScript 将捕获无效更新
375
- const [prefs, updatePrefs] = useImmerKeyStorage(prefsStorage);
365
+ ```typescript jsx
366
+ import { useQueryState } from '@ahoo-wang/fetcher-react';
376
367
 
377
- // 这将导致 TypeScript 错误:
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);
1171
+
1172
+ // 这将导致 TypeScript 错误:
378
1173
  // updatePrefs(draft => { draft.theme = 'invalid'; });
379
1174
  ```
380
1175
 
381
- ## 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
382
1343
 
383
1344
  Wow 查询 Hooks 提供高级数据查询功能,具有内置的状态管理,用于条件、投影、排序、分页和限制。这些 hooks 专为与 `@ahoo-wang/fetcher-wow` 包配合使用而设计,用于复杂的查询操作。
384
1345
 
385
- ### 基础查询 Hooks
1346
+ #### 基础查询 Hooks
386
1347
 
387
- #### useListQuery Hook
1348
+ ##### useListQuery
388
1349
 
389
1350
  `useListQuery` hook 管理列表查询,具有条件、投影、排序和限制的状态管理。
390
1351
 
@@ -395,7 +1356,7 @@ const MyComponent = () => {
395
1356
  const { result, loading, error, execute, setCondition, setLimit } = useListQuery({
396
1357
  initialQuery: { condition: {}, projection: {}, sort: [], limit: 10 },
397
1358
  execute: async (listQuery) => {
398
- // Your list fetching logic here
1359
+ // 您的列表获取逻辑
399
1360
  return fetchListData(listQuery);
400
1361
  },
401
1362
  });
@@ -421,7 +1382,37 @@ const MyComponent = () => {
421
1382
  };
422
1383
  ```
423
1384
 
424
- ### 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
425
1416
 
426
1417
  `usePagedQuery` hook 管理分页查询,具有条件、投影、分页和排序的状态管理。
427
1418
 
@@ -437,7 +1428,7 @@ const MyComponent = () => {
437
1428
  sort: []
438
1429
  },
439
1430
  execute: async (pagedQuery) => {
440
- // Your paged fetching logic here
1431
+ // 您的分页获取逻辑
441
1432
  return fetchPagedData(pagedQuery);
442
1433
  },
443
1434
  });
@@ -468,7 +1459,47 @@ const MyComponent = () => {
468
1459
  };
469
1460
  ```
470
1461
 
471
- ### useSingleQuery Hook
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
472
1503
 
473
1504
  `useSingleQuery` hook 管理单个查询,具有条件、投影和排序的状态管理。
474
1505
 
@@ -484,76 +1515,213 @@ const MyComponent = () => {
484
1515
  },
485
1516
  });
486
1517
 
487
- const handleFetchUser = (userId: string) => {
488
- setCondition({ id: userId });
489
- execute();
490
- };
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>
1613
+ </div>
1614
+ );
1615
+ };
1616
+ ```
1617
+
1618
+ ##### useListStreamQuery
1619
+
1620
+ `useListStreamQuery` hook 管理列表流查询,返回服务器发送事件的 readable stream。
1621
+
1622
+ ```typescript jsx
1623
+ import { useListStreamQuery } from '@ahoo-wang/fetcher-react';
1624
+
1625
+ const MyComponent = () => {
1626
+ const { result, loading, error, execute, setCondition } = useListStreamQuery({
1627
+ initialQuery: { condition: {}, projection: {}, sort: [], limit: 100 },
1628
+ execute: async (listQuery) => {
1629
+ // 您的流获取逻辑
1630
+ return fetchListStream(listQuery);
1631
+ },
1632
+ });
1633
+
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]);
491
1652
 
492
1653
  if (loading) return <div>加载中...</div>;
493
1654
  if (error) return <div>错误: {error.message}</div>;
494
1655
 
495
1656
  return (
496
1657
  <div>
497
- <button onClick={() => handleFetchUser('123')}>获取用户</button>
498
- {result && <p>用户: {result.name}</p>}
1658
+ <button onClick={execute}>开始流</button>
499
1659
  </div>
500
1660
  );
501
1661
  };
502
1662
  ```
503
1663
 
504
- ### useCountQuery Hook
505
-
506
- `useCountQuery` hook 管理计数查询,具有条件的状态管理。
1664
+ ###### 自动执行示例
507
1665
 
508
1666
  ```typescript jsx
509
- import { useCountQuery } from '@ahoo-wang/fetcher-react';
1667
+ import { useListStreamQuery } from '@ahoo-wang/fetcher-react';
510
1668
 
511
1669
  const MyComponent = () => {
512
- const { result, loading, error, execute, setCondition } = useCountQuery({
513
- initialQuery: {},
514
- execute: async (condition) => {
515
- // 您的计数获取逻辑
516
- return fetchCount(condition);
517
- },
1670
+ const { result, loading, error, execute, setCondition } = useListStreamQuery({
1671
+ initialQuery: { condition: {}, projection: {}, sort: [], limit: 100 },
1672
+ execute: async (listQuery) => fetchListStream(listQuery),
1673
+ autoExecute: true, // 组件挂载时自动执行
518
1674
  });
519
1675
 
520
- const handleCountActive = () => {
521
- setCondition({ status: 'active' });
522
- execute();
523
- };
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
+ // 流将在组件挂载时自动启动
524
1696
 
525
1697
  if (loading) return <div>加载中...</div>;
526
1698
  if (error) return <div>错误: {error.message}</div>;
527
1699
 
528
1700
  return (
529
1701
  <div>
530
- <button onClick={handleCountActive}>计数活跃项目</button>
531
- <p>总数: {result}</p>
1702
+ {/* 流已自动启动 */}
532
1703
  </div>
533
1704
  );
534
1705
  };
535
1706
  ```
536
1707
 
537
- ### 获取查询 Hooks
1708
+ #### Fetcher 查询 Hooks
538
1709
 
539
- #### useFetcherCountQuery Hook
1710
+ ##### useFetcherCountQuery
540
1711
 
541
1712
  `useFetcherCountQuery` hook 是使用 Fetcher 库执行计数查询的专用 React hook。它专为需要检索匹配特定条件的记录数量的场景而设计,返回表示计数的数字。
542
1713
 
543
1714
  ```typescript jsx
544
1715
  import { useFetcherCountQuery } from '@ahoo-wang/fetcher-react';
545
1716
  import { all } from '@ahoo-wang/fetcher-wow';
546
-
547
1717
  function UserCountComponent() {
548
1718
  const { data: count, loading, error, execute } = useFetcherCountQuery({
549
1719
  url: '/api/users/count',
550
1720
  initialQuery: all(),
551
1721
  autoExecute: true,
552
1722
  });
553
-
554
1723
  if (loading) return <div>加载中...</div>;
555
1724
  if (error) return <div>错误: {error.message}</div>;
556
-
557
1725
  return (
558
1726
  <div>
559
1727
  <div>活跃用户总数: {count}</div>
@@ -563,23 +1731,19 @@ function UserCountComponent() {
563
1731
  }
564
1732
  ```
565
1733
 
566
- #### 自动执行示例
1734
+ ###### 自动执行示例
567
1735
 
568
1736
  ```typescript jsx
569
1737
  import { useFetcherCountQuery } from '@ahoo-wang/fetcher-react';
570
-
571
1738
  const MyComponent = () => {
572
1739
  const { data: count, loading, error, execute } = useFetcherCountQuery({
573
1740
  url: '/api/users/count',
574
1741
  initialQuery: { status: 'active' },
575
1742
  autoExecute: true, // 组件挂载时自动执行
576
1743
  });
577
-
578
1744
  // 查询将在组件挂载时自动执行
579
-
580
1745
  if (loading) return <div>加载中...</div>;
581
1746
  if (error) return <div>错误: {error.message}</div>;
582
-
583
1747
  return (
584
1748
  <div>
585
1749
  <p>活跃用户总数: {count}</p>
@@ -588,7 +1752,7 @@ const MyComponent = () => {
588
1752
  };
589
1753
  ```
590
1754
 
591
- ### useFetcherPagedQuery Hook
1755
+ ##### useFetcherPagedQuery
592
1756
 
593
1757
  `useFetcherPagedQuery` hook 是使用 Fetcher 库执行分页查询的专用 React hook。它专为需要检索匹配查询条件的分页数据的场景而设计,返回包含当前页面项目以及分页元数据的 PagedList。
594
1758
 
@@ -657,7 +1821,7 @@ function UserListComponent() {
657
1821
  }
658
1822
  ```
659
1823
 
660
- #### 自动执行示例
1824
+ ###### 自动执行示例
661
1825
 
662
1826
  ```typescript jsx
663
1827
  import { useFetcherPagedQuery } from '@ahoo-wang/fetcher-react';
@@ -693,7 +1857,7 @@ const MyComponent = () => {
693
1857
  };
694
1858
  ```
695
1859
 
696
- ### useFetcherListQuery Hook
1860
+ ##### useFetcherListQuery
697
1861
 
698
1862
  `useFetcherListQuery` hook 是使用 Fetcher 库执行列表查询的专用 React hook。它专为获取项目列表而设计,支持通过 ListQuery 类型进行过滤、排序和分页,返回结果数组。
699
1863
 
@@ -754,7 +1918,7 @@ function UserListComponent() {
754
1918
  }
755
1919
  ```
756
1920
 
757
- #### 自动执行示例
1921
+ ###### 自动执行示例
758
1922
 
759
1923
  ```typescript jsx
760
1924
  import { useFetcherListQuery } from '@ahoo-wang/fetcher-react';
@@ -789,7 +1953,7 @@ const MyComponent = () => {
789
1953
  };
790
1954
  ```
791
1955
 
792
- ### useFetcherListStreamQuery Hook
1956
+ ##### useFetcherListStreamQuery
793
1957
 
794
1958
  `useFetcherListStreamQuery` hook 是使用 Fetcher 库通过服务器发送事件执行列表流查询的专用 React hook。它专为需要检索匹配列表查询条件的数据流场景而设计,返回 JSON 服务器发送事件的 ReadableStream,用于实时数据流式传输。
795
1959
 
@@ -852,7 +2016,7 @@ function UserStreamComponent() {
852
2016
  }
853
2017
  ```
854
2018
 
855
- #### 自动执行示例
2019
+ ###### 自动执行示例
856
2020
 
857
2021
  ```typescript jsx
858
2022
  import { useFetcherListStreamQuery } from '@ahoo-wang/fetcher-react';
@@ -908,7 +2072,7 @@ const MyComponent = () => {
908
2072
  };
909
2073
  ```
910
2074
 
911
- ### useFetcherSingleQuery Hook
2075
+ ##### useFetcherSingleQuery
912
2076
 
913
2077
  `useFetcherSingleQuery` hook 是使用 Fetcher 库执行单个项目查询的专用 React hook。它专为获取单个项目而设计,支持通过 SingleQuery 类型进行过滤和排序,返回单个结果项目。
914
2078
 
@@ -952,7 +2116,7 @@ function UserProfileComponent({ userId }: { userId: string }) {
952
2116
  }
953
2117
  ```
954
2118
 
955
- #### 自动执行示例
2119
+ ###### 自动执行示例
956
2120
 
957
2121
  ```typescript jsx
958
2122
  import { useFetcherSingleQuery } from '@ahoo-wang/fetcher-react';
@@ -968,95 +2132,519 @@ const MyComponent = () => {
968
2132
  autoExecute: true, // 组件挂载时自动执行
969
2133
  });
970
2134
 
971
- // 查询将在组件挂载时自动执行
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>
2278
+ );
2279
+ }
2280
+ ```
2281
+
2282
+ ### Suspense 集成
2283
+
2284
+ 与 React Suspense 一起使用以获得更好的加载状态:
2285
+
2286
+ ```typescript jsx
2287
+ import { Suspense, useState } from 'react';
2288
+ import { useFetcher } from '@ahoo-wang/fetcher-react';
2289
+
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
+ };
2318
+ }
2319
+
2320
+ function DataComponent({ resource }: { resource: any }) {
2321
+ const data = resource.read(); // 如果待处理将抛出
2322
+ return <div>{JSON.stringify(data)}</div>;
2323
+ }
2324
+
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
+ };
2333
+
2334
+ return (
2335
+ <div>
2336
+ <button onClick={handleFetch}>获取数据</button>
2337
+ <Suspense fallback={<div>加载中...</div>}>
2338
+ {resource && <DataComponent resource={resource} />}
2339
+ </Suspense>
2340
+ </div>
2341
+ );
2342
+ }
2343
+ ```
2344
+
2345
+ ### 性能优化模式
2346
+
2347
+ 优化性能的高级模式:
2348
+
2349
+ ```typescript jsx
2350
+ import { useMemo, useCallback, useRef } from 'react';
2351
+ import { useListQuery } from '@ahoo-wang/fetcher-react';
2352
+
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
2369
+ });
2370
+
2371
+ // 使用 ref 跟踪最新过滤器而不引起重新渲染
2372
+ const filtersRef = useRef(filters);
2373
+
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
+ );
2385
+
2386
+ return (
2387
+ <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
+ )}
2403
+ </div>
2404
+ );
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
+ }
2418
+ ```
2419
+
2420
+ ### 真实世界集成示例
2421
+
2422
+ 显示与流行库集成的完整示例:
2423
+
2424
+ #### 与 React Query (TanStack Query) 集成
2425
+
2426
+ ```typescript jsx
2427
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
2428
+ import { useFetcher } from '@ahoo-wang/fetcher-react';
2429
+
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
+ }
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);
2494
+
2495
+ useEffect(() => {
2496
+ dispatch(fetchUserData(userId));
2497
+ }, [userId, dispatch]);
972
2498
 
973
2499
  if (loading) return <div>加载中...</div>;
974
- if (error) return <div>错误: {error.message}</div>;
975
- if (!product) return <div>未找到产品</div>;
2500
+ if (error) return <div>错误: {error}</div>;
976
2501
 
977
- return (
978
- <div>
979
- <h2>特色产品</h2>
980
- <div>{product.name}</div>
981
- <div>{product.description}</div>
982
- </div>
983
- );
984
- };
2502
+ return <div>{data?.name}</div>;
2503
+ }
985
2504
  ```
986
2505
 
987
- ### 流查询 Hooks
2506
+ #### Zustand 集成
988
2507
 
989
- #### useListStreamQuery Hook
2508
+ ```typescript jsx
2509
+ import { create } from 'zustand';
2510
+ import { useFetcher } from '@ahoo-wang/fetcher-react';
990
2511
 
991
- `useListStreamQuery` hook 管理列表流查询,返回服务器发送事件的 readable stream。
2512
+ interface UserStore {
2513
+ user: any;
2514
+ loading: boolean;
2515
+ error: string | null;
2516
+ fetchUser: (userId: string) => Promise<void>;
2517
+ }
992
2518
 
993
- ```typescript jsx
994
- import { useListStreamQuery } from '@ahoo-wang/fetcher-react';
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 });
2534
+ }
2535
+ }
2536
+ }));
995
2537
 
996
- const MyComponent = () => {
997
- const { result, loading, error, execute, setCondition } = useListStreamQuery({
998
- initialQuery: { condition: {}, projection: {}, sort: [], limit: 100 },
999
- execute: async (listQuery) => {
1000
- // 您的流获取逻辑
1001
- return fetchListStream(listQuery);
1002
- },
1003
- });
2538
+ function UserComponent({ userId }: { userId: string }) {
2539
+ const { user, loading, error, fetchUser } = useUserStore();
1004
2540
 
1005
2541
  useEffect(() => {
1006
- if (result) {
1007
- const reader = result.getReader();
1008
- const readStream = async () => {
1009
- try {
1010
- while (true) {
1011
- const { done, value } = await reader.read();
1012
- if (done) break;
1013
- console.log('接收到:', value);
1014
- // 处理流事件
1015
- }
1016
- } catch (error) {
1017
- console.error('流错误:', error);
1018
- }
1019
- };
1020
- readStream();
1021
- }
1022
- }, [result]);
2542
+ fetchUser(userId);
2543
+ }, [userId, fetchUser]);
1023
2544
 
1024
2545
  if (loading) return <div>加载中...</div>;
1025
- if (error) return <div>错误: {error.message}</div>;
2546
+ if (error) return <div>错误: {error}</div>;
1026
2547
 
1027
- return (
1028
- <div>
1029
- <button onClick={execute}>开始流</button>
1030
- </div>
1031
- );
1032
- };
2548
+ return <div>{user?.name}</div>;
2549
+ }
1033
2550
  ```
1034
2551
 
1035
- ## 最佳实践
2552
+ ### 测试模式
1036
2553
 
1037
- ### 性能优化
2554
+ Hooks 的综合测试示例:
1038
2555
 
1039
- - 谨慎使用 `autoExecute: true`,避免在挂载时进行不必要的请求
1040
- - 当启用 `autoExecute` 时,使用 `setQuery` 更新查询以触发自动重新执行
1041
- - `execute` 函数中记忆化昂贵的计算
2556
+ ```typescript jsx
2557
+ import { renderHook, act, waitFor } from '@testing-library/react';
2558
+ import { useFetcher, useListQuery } from '@ahoo-wang/fetcher-react';
1042
2559
 
1043
- ### 错误处理
2560
+ // 模拟 fetcher
2561
+ jest.mock('@ahoo-wang/fetcher', () => ({
2562
+ Fetcher: jest.fn().mockImplementation(() => ({
2563
+ request: jest.fn(),
2564
+ })),
2565
+ }));
1044
2566
 
1045
- - 始终在组件中处理加载和错误状态
1046
- - 使用自定义错误类型以更好地分类错误
1047
- - 为瞬时故障实现重试逻辑
2567
+ describe('useFetcher', () => {
2568
+ it('应处理成功的获取', async () => {
2569
+ const mockData = { id: 1, name: 'Test' };
2570
+ const mockFetcher = { request: jest.fn().mockResolvedValue(mockData) };
1048
2571
 
1049
- ### 类型安全
2572
+ const { result } = renderHook(() => useFetcher({ fetcher: mockFetcher }));
1050
2573
 
1051
- - 为查询参数和结果定义严格的接口
1052
- - 在整个应用程序中一致使用泛型类型
1053
- - 启用严格 TypeScript 模式以获得最大安全性
2574
+ act(() => {
2575
+ result.current.execute({ url: '/api/test', method: 'GET' });
2576
+ });
1054
2577
 
1055
- ### 状态管理
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
+ });
1056
2584
 
1057
- - 与全局状态管理结合使用(Redux、Zustand)以处理复杂应用
1058
- - 使用 `useKeyStorage` 进行持久化的客户端数据存储
1059
- - 实现乐观更新以改善用户体验
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
+ ```
1060
2648
 
1061
2649
  ## API 参考
1062
2650
 
@@ -1071,7 +2659,7 @@ function useDebouncedCallback<T extends (...args: any[]) => any>(
1071
2659
  ): UseDebouncedCallbackReturn<T>;
1072
2660
  ```
1073
2661
 
1074
- 一个 React hook,为回调函数提供防抖版本,支持前缘/后缘执行选项。
2662
+ 为回调函数提供防抖版本的 React hook,支持前缘/后缘执行选项。
1075
2663
 
1076
2664
  **类型参数:**
1077
2665
 
@@ -1167,208 +2755,50 @@ function useDebouncedFetcher<R, E = FetcherError>(
1167
2755
  function useDebouncedFetcherQuery<Q, R, E = FetcherError>(
1168
2756
  options: UseDebouncedFetcherQueryOptions<Q, R, E>,
1169
2757
  ): UseDebouncedFetcherQueryReturn<Q, R, E>;
1170
- ```
1171
-
1172
- 将基于查询的 HTTP 获取与防抖相结合,非常适合搜索输入和动态查询场景,您希望根据查询参数防抖 API 调用。
1173
-
1174
- ```typescript jsx
1175
- import { useDebouncedFetcherQuery } from '@ahoo-wang/fetcher-react';
1176
-
1177
- interface SearchQuery {
1178
- keyword: string;
1179
- limit: number;
1180
- filters?: { category?: string };
1181
- }
1182
-
1183
- interface SearchResult {
1184
- items: Array<{ id: string; title: string }>;
1185
- total: number;
1186
- }
1187
-
1188
- const SearchComponent = () => {
1189
- const {
1190
- loading,
1191
- result,
1192
- error,
1193
- run,
1194
- cancel,
1195
- isPending,
1196
- setQuery,
1197
- getQuery,
1198
- } = useDebouncedFetcherQuery<SearchQuery, SearchResult>({
1199
- url: '/api/search',
1200
- initialQuery: { keyword: '', limit: 10 },
1201
- debounce: { delay: 300 }, // 防抖 300ms
1202
- autoExecute: false, // 挂载时不执行
1203
- });
1204
-
1205
- const handleSearch = (keyword: string) => {
1206
- setQuery({ keyword, limit: 10 }); // 如果 autoExecute 为 true,这将触发防抖执行
1207
- };
1208
-
1209
- const handleManualSearch = () => {
1210
- run(); // 使用当前查询手动防抖执行
1211
- };
1212
-
1213
- const handleCancel = () => {
1214
- cancel(); // 取消任何待处理的防抖执行
1215
- };
1216
-
1217
- if (loading) return <div>搜索中...</div>;
1218
- if (error) return <div>错误: {error.message}</div>;
1219
-
1220
- return (
1221
- <div>
1222
- <input
1223
- type="text"
1224
- onChange={(e) => handleSearch(e.target.value)}
1225
- placeholder="搜索..."
1226
- />
1227
- <button onClick={handleManualSearch} disabled={isPending()}>
1228
- {isPending() ? '搜索中...' : '搜索'}
1229
- </button>
1230
- <button onClick={handleCancel}>取消</button>
1231
- {result && (
1232
- <div>
1233
- 找到 {result.total} 项:
1234
- {result.items.map(item => (
1235
- <div key={item.id}>{item.title}</div>
1236
- ))}
1237
- </div>
1238
- )}
1239
- </div>
1240
- );
1241
- };
1242
- ```
1243
-
1244
- **主要特性:**
1245
-
1246
- - **查询状态管理**: 使用 `setQuery` 和 `getQuery` 自动查询参数处理
1247
- - **防抖执行**: 在快速用户输入期间防止过多 API 调用
1248
- - **自动执行**: 可选的在查询参数更改时自动执行
1249
- - **手动控制**: `run()` 用于手动执行,`cancel()` 用于取消
1250
- - **待处理状态**: `isPending()` 检查防抖调用是否排队
1251
-
1252
- **类型参数:**
1253
-
1254
- - `Q`: 查询参数的类型
1255
- - `R`: 获取结果的类型
1256
- - `E`: 错误的类型(默认为 FetcherError)
1257
-
1258
- **参数:**
1259
-
1260
- - `options`: 扩展 `UseFetcherQueryOptions` 和 `DebounceCapable` 的配置对象
1261
- - `url`: API 端点 URL(必需)
1262
- - `initialQuery`: 初始查询参数(必需)
1263
- - `autoExecute?`: 查询参数更改时是否自动执行
1264
- - `debounce`: 防抖配置(delay、leading、trailing)
1265
- - HTTP 请求选项(method、headers、timeout 等)
1266
-
1267
- **返回:**
1268
-
1269
- 包含以下内容的对象:
1270
-
1271
- - `loading`: 布尔值,表示获取当前是否正在执行
1272
- - `result`: 获取的解析值
1273
- - `error`: 执行期间发生的任何错误
1274
- - `status`: 当前执行状态
1275
- - `reset`: 重置获取器状态的函数
1276
- - `abort`: 中止当前操作的函数
1277
- - `getQuery`: 获取当前查询参数的函数
1278
- - `setQuery`: 更新查询参数的函数
1279
- - `run`: 使用当前查询执行防抖获取的函数
1280
- - `cancel`: 取消任何待处理防抖执行的函数
1281
- - `isPending`: 返回布尔值表示防抖执行当前是否待处理的函数
1282
-
1283
- #### useDebouncedQuery
1284
-
1285
- 将通用查询执行与防抖相结合,非常适合自定义查询操作,您希望根据查询参数防抖执行。
1286
-
1287
- ```typescript jsx
1288
- import { useDebouncedQuery } from '@ahoo-wang/fetcher-react';
1289
-
1290
- interface SearchQuery {
1291
- keyword: string;
1292
- limit: number;
1293
- filters?: { category?: string };
1294
- }
1295
-
1296
- interface SearchResult {
1297
- items: Array<{ id: string; title: string }>;
1298
- total: number;
1299
- }
1300
-
1301
- const SearchComponent = () => {
1302
- const {
1303
- loading,
1304
- result,
1305
- error,
1306
- run,
1307
- cancel,
1308
- isPending,
1309
- setQuery,
1310
- getQuery,
1311
- } = useDebouncedQuery<SearchQuery, SearchResult>({
1312
- initialQuery: { keyword: '', limit: 10 },
1313
- execute: async (query) => {
1314
- const response = await fetch('/api/search', {
1315
- method: 'POST',
1316
- body: JSON.stringify(query),
1317
- headers: { 'Content-Type': 'application/json' },
1318
- });
1319
- return response.json();
1320
- },
1321
- debounce: { delay: 300 }, // 防抖 300ms
1322
- autoExecute: false, // 挂载时不执行
1323
- });
2758
+ ```
1324
2759
 
1325
- const handleSearch = (keyword: string) => {
1326
- setQuery({ keyword, limit: 10 }); // 如果 autoExecute 为 true,这将触发防抖执行
1327
- };
2760
+ 将基于查询的 HTTP 获取与防抖相结合,非常适合搜索输入和动态查询场景,您希望根据查询参数防抖 API 调用。
1328
2761
 
1329
- const handleManualSearch = () => {
1330
- run(); // 使用当前查询手动防抖执行
1331
- };
2762
+ **类型参数:**
1332
2763
 
1333
- const handleCancel = () => {
1334
- cancel(); // 取消任何待处理的防抖执行
1335
- };
2764
+ - `Q`: 查询参数的类型
2765
+ - `R`: 获取结果的类型
2766
+ - `E`: 错误的类型(默认为 FetcherError)
1336
2767
 
1337
- if (loading) return <div>搜索中...</div>;
1338
- if (error) return <div>错误: {error.message}</div>;
2768
+ **参数:**
1339
2769
 
1340
- return (
1341
- <div>
1342
- <input
1343
- type="text"
1344
- onChange={(e) => handleSearch(e.target.value)}
1345
- placeholder="搜索..."
1346
- />
1347
- <button onClick={handleManualSearch} disabled={isPending()}>
1348
- {isPending() ? '搜索中...' : '搜索'}
1349
- </button>
1350
- <button onClick={handleCancel}>取消</button>
1351
- {result && (
1352
- <div>
1353
- 找到 {result.total} 项:
1354
- {result.items.map(item => (
1355
- <div key={item.id}>{item.title}</div>
1356
- ))}
1357
- </div>
1358
- )}
1359
- </div>
1360
- );
1361
- };
1362
- ```
2770
+ - `options`: 扩展 `UseFetcherQueryOptions` 和 `DebounceCapable` 的配置对象
2771
+ - `url`: API 端点 URL(必需)
2772
+ - `initialQuery`: 初始查询参数(必需)
2773
+ - `autoExecute?`: 查询参数更改时是否自动执行
2774
+ - `debounce`: 防抖配置(delay、leading、trailing)
2775
+ - HTTP 请求选项(method、headers、timeout 等)
1363
2776
 
1364
- **主要特性:**
2777
+ **返回:**
1365
2778
 
1366
- - **查询状态管理**: 使用 `setQuery` 和 `getQuery` 自动查询参数处理
1367
- - **防抖执行**: 在快速查询更改期间防止过多操作
1368
- - **自动执行**: 可选的在查询参数更改时自动执行
1369
- - **手动控制**: `run()` 用于手动执行,`cancel()` 用于取消
1370
- - **待处理状态**: `isPending()` 检查防抖调用是否排队
1371
- - **自定义执行**: 灵活的 execute 函数用于任何查询操作
2779
+ 包含以下内容的对象:
2780
+
2781
+ - `loading`: 布尔值,表示获取当前是否正在执行
2782
+ - `result`: 获取的解析值
2783
+ - `error`: 执行期间发生的任何错误
2784
+ - `status`: 当前执行状态
2785
+ - `reset`: 重置获取器状态的函数
2786
+ - `abort`: 中止当前操作的函数
2787
+ - `getQuery`: 获取当前查询参数的函数
2788
+ - `setQuery`: 更新查询参数的函数
2789
+ - `run`: 使用当前查询执行防抖获取的函数
2790
+ - `cancel`: 取消任何待处理防抖执行的函数
2791
+ - `isPending`: 返回布尔值表示防抖执行当前是否待处理的函数
2792
+
2793
+ #### useDebouncedQuery
2794
+
2795
+ ```typescript
2796
+ function useDebouncedQuery<Q, R, E = FetcherError>(
2797
+ options: UseDebouncedQueryOptions<Q, R, E>,
2798
+ ): UseDebouncedQueryReturn<Q, R, E>;
2799
+ ```
2800
+
2801
+ 将通用查询执行与防抖相结合,非常适合自定义查询操作,您希望根据查询参数防抖执行。
1372
2802
 
1373
2803
  **类型参数:**
1374
2804
 
@@ -1503,9 +2933,7 @@ function usePromiseState<R = unknown, E = unknown>(
1503
2933
  - `setError`: 设置状态为 ERROR 并提供错误
1504
2934
  - `setIdle`: 设置状态为 IDLE
1505
2935
 
1506
- ### 工具 Hooks
1507
-
1508
- #### useRequestId
2936
+ ### useRequestId
1509
2937
 
1510
2938
  ```typescript
1511
2939
  function useRequestId(): UseRequestIdReturn;
@@ -1576,191 +3004,170 @@ React hook,用于使用 Map-like 接口管理多个 refs,允许通过键动
1576
3004
  - `RefKey = string | number | symbol`
1577
3005
  - `UseRefsReturn<T> extends Iterable<[RefKey, T]>`
1578
3006
 
1579
- ### 事件 Hooks
3007
+ ### useQuery
1580
3008
 
1581
- #### useEventSubscription Hook
3009
+ ```typescript
3010
+ function useQuery<Q, R, E = FetcherError>(
3011
+ options: UseQueryOptions<Q, R, E>,
3012
+ ): UseQueryReturn<Q, R, E>;
3013
+ ```
1582
3014
 
1583
- `useEventSubscription` hook 为类型化事件总线提供了 React 接口。它自动管理订阅生命周期,同时提供手动控制功能以增加灵活性。
3015
+ 用于管理基于查询的异步操作的 React hook,具有自动状态管理和执行控制。
1584
3016
 
1585
- ```typescript jsx
1586
- import { useEventSubscription } from '@ahoo-wang/fetcher-react';
1587
- import { eventBus } from './eventBus';
3017
+ **类型参数:**
1588
3018
 
1589
- function MyComponent() {
1590
- const { subscribe, unsubscribe } = useEventSubscription({
1591
- bus: eventBus,
1592
- handler: {
1593
- name: 'myEvent',
1594
- handle: (event) => {
1595
- console.log('收到事件:', event);
1596
- }
1597
- }
1598
- });
3019
+ - `Q`: 查询参数的类型
3020
+ - `R`: 结果值的类型
3021
+ - `E`: 错误值的类型(默认为 FetcherError)
1599
3022
 
1600
- // hook 在组件挂载时自动订阅,在卸载时自动取消订阅
1601
- // 如需要,您也可以手动控制订阅
1602
- const handleToggleSubscription = () => {
1603
- if (someCondition) {
1604
- subscribe();
1605
- } else {
1606
- unsubscribe();
1607
- }
1608
- };
3023
+ **参数:**
1609
3024
 
1610
- return <div>我的组件</div>;
1611
- }
1612
- ```
3025
+ - `options`: 查询的配置选项
3026
+ - `initialQuery`: 初始查询参数
3027
+ - `execute`: 使用给定参数和可选属性执行查询的函数
3028
+ - `autoExecute?`: 是否在挂载和查询更改时自动执行查询
3029
+ - 所有来自 `UseExecutePromiseOptions` 的选项
1613
3030
 
1614
- 关键特性:
3031
+ **返回值:**
1615
3032
 
1616
- - **自动生命周期管理**: 在组件挂载时自动订阅,在卸载时自动取消订阅
1617
- - **手动控制**: 提供 `subscribe` 和 `unsubscribe` 函数以进行额外控制
1618
- - **类型安全**: 完全支持 TypeScript,具有泛型事件类型
1619
- - **错误处理**: 对失败的订阅尝试记录警告
1620
- - **事件总线集成**: 与 `@ahoo-wang/fetcher-eventbus` TypedEventBus 实例无缝配合
3033
+ 包含查询状态和控制函数的对象:
1621
3034
 
1622
- ### CoSec 安全 Hooks
3035
+ - `loading`: 布尔值,指示查询当前是否正在执行
3036
+ - `result`: 查询的解析值
3037
+ - `error`: 执行期间发生的任何错误
3038
+ - `status`: 当前执行状态
3039
+ - `execute`: 使用当前参数执行查询的函数
3040
+ - `reset`: 重置 Promise 状态的函数
3041
+ - `abort`: 中止当前操作的函数
3042
+ - `getQuery`: 检索当前查询参数的函数
3043
+ - `setQuery`: 更新查询参数的函数
1623
3044
 
1624
- 🛡️ **企业安全集成** - 强大的 React hooks,用于使用 CoSec 令牌管理认证状态,提供与企业安全系统的无缝集成和自动令牌生命周期管理。
3045
+ ### useQueryState
1625
3046
 
1626
- #### useSecurity Hook
3047
+ ```typescript
3048
+ function useQueryState<Q>(
3049
+ options: UseQueryStateOptions<Q>,
3050
+ ): UseQueryStateReturn<Q>;
3051
+ ```
1627
3052
 
1628
- `useSecurity` hook 使用 CoSec 令牌提供对认证状态和操作的响应式访问。它与 TokenStorage 集成以持久化令牌,并在令牌更改时响应式更新状态。
3053
+ 用于管理查询状态的 React hook,具有自动执行功能。
1629
3054
 
1630
- ```typescript jsx
1631
- import { useSecurity } from '@ahoo-wang/fetcher-react/cosec';
1632
- import { tokenStorage } from './tokenStorage';
1633
- import { useNavigate } from 'react-router-dom';
3055
+ **类型参数:**
1634
3056
 
1635
- function App() {
1636
- const navigate = useNavigate();
3057
+ - `Q`: 查询参数的类型
1637
3058
 
1638
- const { currentUser, authenticated, signIn, signOut } = useSecurity(tokenStorage, {
1639
- onSignIn: () => {
1640
- // 登录成功后重定向到仪表板
1641
- navigate('/dashboard');
1642
- },
1643
- onSignOut: () => {
1644
- // 登出后重定向到登录页面
1645
- navigate('/login');
1646
- }
1647
- });
3059
+ **参数:**
1648
3060
 
1649
- const handleSignIn = async () => {
1650
- // 直接令牌
1651
- await signIn(compositeToken);
3061
+ - `options`: hook 的配置选项
3062
+ - `initialQuery`: 要存储和管理的初始查询参数
3063
+ - `autoExecute?`: 是否在查询更改时或组件挂载时自动执行
3064
+ - `execute`: 使用当前查询参数执行的函数
1652
3065
 
1653
- // 或异步函数
1654
- await signIn(async () => {
1655
- const response = await fetch('/api/auth/login', {
1656
- method: 'POST',
1657
- body: JSON.stringify({ username, password })
1658
- });
1659
- return response.json();
1660
- });
1661
- };
3066
+ **返回值:**
1662
3067
 
1663
- if (!authenticated) {
1664
- return <button onClick={handleSignIn}>登录</button>;
1665
- }
3068
+ 包含以下内容的对象:
1666
3069
 
1667
- return (
1668
- <div>
1669
- <p>欢迎, {currentUser.sub}!</p>
1670
- <button onClick={signOut}>登出</button>
1671
- </div>
1672
- );
1673
- }
1674
- ```
3070
+ - `getQuery`: 检索当前查询参数的函数
3071
+ - `setQuery`: 更新查询参数的函数。如果 autoExecute 为 true 会触发执行
1675
3072
 
1676
- **关键特性:**
3073
+ ### useMounted
1677
3074
 
1678
- - **响应式认证状态**: 当令牌更改时自动更新
1679
- - **灵活的登录方法**: 支持直接令牌和异步令牌提供者
1680
- - **生命周期回调**: 可配置的登录和登出事件回调
1681
- - **类型安全**: 完全支持 TypeScript,具有 CoSec JWT 负载类型
1682
- - **令牌持久化**: 与 TokenStorage 集成以实现跨会话持久化
3075
+ ```typescript
3076
+ function useMounted(): () => boolean;
3077
+ ```
1683
3078
 
1684
- #### SecurityProvider
3079
+ 返回检查组件是否仍挂载的函数的 React hook。
1685
3080
 
1686
- `SecurityProvider` 组件包装您的应用程序以通过 React 上下文提供认证上下文。它在内部使用 `useSecurity` hook,并通过 `useSecurityContext` hook 使认证状态可用于所有子组件。
3081
+ **返回值:**
1687
3082
 
1688
- ```tsx
1689
- import { SecurityProvider } from '@ahoo-wang/fetcher-react';
1690
- import { tokenStorage } from './tokenStorage';
1691
- import { useNavigate } from 'react-router-dom';
3083
+ 当组件仍挂载时返回 `true`,否则返回 `false` 的函数。
1692
3084
 
1693
- function App() {
1694
- const navigate = useNavigate();
3085
+ ### useForceUpdate
1695
3086
 
1696
- return (
1697
- <SecurityProvider
1698
- tokenStorage={tokenStorage}
1699
- onSignIn={() => navigate('/dashboard')}
1700
- onSignOut={() => navigate('/login')}
1701
- >
1702
- <MyApp />
1703
- </SecurityProvider>
1704
- );
1705
- }
3087
+ ```typescript
3088
+ function useForceUpdate(): () => void;
1706
3089
  ```
1707
3090
 
1708
- **配置选项:**
3091
+ 返回强制组件重新渲染的函数的 React hook。
1709
3092
 
1710
- - `tokenStorage`: 用于管理认证令牌的 TokenStorage 实例
1711
- - `onSignIn`: 登录成功时调用的回调函数
1712
- - `onSignOut`: 登出时调用的回调函数
1713
- - `children`: 将有权访问安全上下文的子组件
3093
+ **返回值:**
1714
3094
 
1715
- #### useSecurityContext Hook
3095
+ 调用时强制组件重新渲染的函数。
1716
3096
 
1717
- `useSecurityContext` hook 在被 `SecurityProvider` 包装的组件中提供对认证状态和方法的访问。它通过 React 上下文提供与 `useSecurity` 相同的接口。
3097
+ ### useEventSubscription
1718
3098
 
1719
- ```tsx
1720
- import { useSecurityContext } from '@ahoo-wang/fetcher-react';
3099
+ ```typescript
3100
+ function useEventSubscription<EVENT = unknown>(
3101
+ options: UseEventSubscriptionOptions<EVENT>,
3102
+ ): UseEventSubscriptionReturn;
3103
+ ```
1721
3104
 
1722
- function UserProfile() {
1723
- const { currentUser, authenticated, signOut } = useSecurityContext();
3105
+ 为类型化事件总线提供 React 接口的 hook。自动管理订阅生命周期,同时提供手动控制功能。
1724
3106
 
1725
- if (!authenticated) {
1726
- return <div>请登录</div>;
1727
- }
3107
+ **类型参数:**
1728
3108
 
1729
- return (
1730
- <div>
1731
- <p>欢迎, {currentUser.sub}!</p>
1732
- <button onClick={signOut}>登出</button>
1733
- </div>
1734
- );
1735
- }
1736
- ```
3109
+ - `EVENT`: 事件总线处理的事件类型(默认为 unknown)
1737
3110
 
1738
- **上下文优势:**
3111
+ **参数:**
1739
3112
 
1740
- - **消除属性钻取**: 无需传递属性即可访问认证状态
1741
- - **组件隔离**: 无论组件树深度如何,组件都可以访问认证状态
1742
- - **集中式状态**: 应用程序中认证的单一真实来源
1743
- - **自动重新渲染**: 当认证状态更改时,组件自动重新渲染
3113
+ - `options`: 订阅的配置选项
3114
+ - `bus`: 要订阅的 TypedEventBus 实例
3115
+ - `handler`: 具有名称和处理方法的事件处理函数
1744
3116
 
1745
- ### 存储 Hooks
3117
+ **返回值:**
1746
3118
 
1747
- #### useKeyStorage
3119
+ 包含以下内容的对象:
1748
3120
 
1749
- ```typescript jsx
3121
+ - `subscribe`: 手动订阅事件总线的函数(返回布尔值成功状态)
3122
+ - `unsubscribe`: 手动取消订阅事件总线的函数(返回布尔值成功状态)
3123
+
3124
+ **相关类型:**
3125
+
3126
+ - `UseEventSubscriptionOptions<EVENT>`: 具有 bus 和 handler 属性的配置接口
3127
+ - `UseEventSubscriptionReturn`: 具有 subscribe 和 unsubscribe 方法的返回接口
3128
+
3129
+ ### useKeyStorage
3130
+
3131
+ ```typescript
3132
+ // 不使用默认值 - 可能返回 null
1750
3133
  function useKeyStorage<T>(
1751
3134
  keyStorage: KeyStorage<T>,
1752
3135
  ): [T | null, (value: T) => void];
3136
+
3137
+ // 使用默认值 - 保证非空
3138
+ function useKeyStorage<T>(
3139
+ keyStorage: KeyStorage<T>,
3140
+ defaultValue: T,
3141
+ ): [T, (value: T) => void];
1753
3142
  ```
1754
3143
 
1755
- 为 KeyStorage 实例提供状态管理的 React hook
3144
+ 为 KeyStorage 实例提供响应式状态管理的 React hook。订阅存储更改并返回当前值以及设置器函数。可选择接受默认值以在存储为空时使用。
3145
+
3146
+ **类型参数:**
3147
+
3148
+ - `T`: 存储在键存储中的值的类型
1756
3149
 
1757
3150
  **参数:**
1758
3151
 
1759
- - `keyStorage`: 要订阅和管理的 KeyStorage 实例
3152
+ - `keyStorage`: 要订阅和管理的 KeyStorage 实例。应该是稳定引用(useRef、memo 或模块级实例)
3153
+ - `defaultValue` _(可选)_: 当存储为空时使用的默认值。提供时,hook 保证返回的值永远不会为 null
1760
3154
 
1761
3155
  **返回值:**
1762
3156
 
1763
- - 包含当前存储值和更新函数的元组
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
+ ```
1764
3171
 
1765
3172
  ### useImmerKeyStorage
1766
3173
 
@@ -1785,11 +3192,11 @@ function useImmerKeyStorage<T>(
1785
3192
 
1786
3193
  **类型参数:**
1787
3194
 
1788
- - `T`: 存储值的数据类型
3195
+ - `T`: 存储在键存储中的值的类型
1789
3196
 
1790
3197
  **参数:**
1791
3198
 
1792
- - `keyStorage`: 要订阅和管理的 KeyStorage 实例。应该是稳定的引用(useRef、memo 或模块级实例)
3199
+ - `keyStorage`: 要订阅和管理的 KeyStorage 实例。应该是稳定引用(useRef、memo 或模块级实例)
1793
3200
  - `defaultValue` _(可选)_: 当存储为空时使用的默认值。提供时,hook 保证返回的值永远不会为 null
1794
3201
 
1795
3202
  **返回值:**
@@ -1848,7 +3255,7 @@ function useListQuery<R, FIELDS extends string = string, E = FetcherError>(
1848
3255
  **参数:**
1849
3256
 
1850
3257
  - `options`: 包含 initialQuery 和 list 函数的配置选项
1851
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3258
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1852
3259
 
1853
3260
  **返回值:**
1854
3261
 
@@ -1873,7 +3280,7 @@ function usePagedQuery<R, FIELDS extends string = string, E = unknown>(
1873
3280
  **参数:**
1874
3281
 
1875
3282
  - `options`: 包含 initialQuery 和 query 函数的配置选项
1876
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3283
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1877
3284
 
1878
3285
  **返回值:**
1879
3286
 
@@ -1898,7 +3305,7 @@ function useSingleQuery<R, FIELDS extends string = string, E = unknown>(
1898
3305
  **参数:**
1899
3306
 
1900
3307
  - `options`: 包含 initialQuery 和 query 函数的配置选项
1901
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3308
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1902
3309
 
1903
3310
  **返回值:**
1904
3311
 
@@ -1922,7 +3329,7 @@ function useCountQuery<FIELDS extends string = string, E = FetcherError>(
1922
3329
  **参数:**
1923
3330
 
1924
3331
  - `options`: 包含 initialQuery 和 execute 函数的配置选项
1925
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3332
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1926
3333
 
1927
3334
  **返回值:**
1928
3335
 
@@ -1948,7 +3355,7 @@ function useFetcherCountQuery<FIELDS extends string = string, E = FetcherError>(
1948
3355
  - `options`: 计数查询的配置选项,包括条件、fetcher 实例和其他查询设置
1949
3356
  - `url`: 从中获取计数的 URL
1950
3357
  - `initialQuery`: 计数查询的初始条件
1951
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3358
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1952
3359
 
1953
3360
  **返回值:**
1954
3361
 
@@ -1979,7 +3386,7 @@ function useFetcherPagedQuery<
1979
3386
  - `options`: 分页查询的配置选项,包括分页查询参数、fetcher 实例和其他查询设置
1980
3387
  - `url`: 从中获取分页数据的 URL
1981
3388
  - `initialQuery`: 初始分页查询配置
1982
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3389
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
1983
3390
 
1984
3391
  **返回值:**
1985
3392
 
@@ -2010,7 +3417,7 @@ function useFetcherListQuery<
2010
3417
  - `options`: 列表查询的配置选项,包括列表查询参数、fetcher 实例和其他查询设置
2011
3418
  - `url`: 从中获取列表数据的 URL
2012
3419
  - `initialQuery`: 初始列表查询配置
2013
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3420
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
2014
3421
 
2015
3422
  **返回值:**
2016
3423
 
@@ -2041,7 +3448,7 @@ function useFetcherListStreamQuery<
2041
3448
  - `options`: 列表流查询的配置选项,包括列表查询参数、fetcher 实例和其他查询设置
2042
3449
  - `url`: 从中获取流数据的 URL
2043
3450
  - `initialQuery`: 初始列表查询配置
2044
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3451
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
2045
3452
 
2046
3453
  **返回值:**
2047
3454
 
@@ -2072,7 +3479,7 @@ function useFetcherSingleQuery<
2072
3479
  - `options`: 单个查询的配置选项,包括单个查询参数、fetcher 实例和其他查询设置
2073
3480
  - `url`: 从中获取单个项目的 URL
2074
3481
  - `initialQuery`: 初始单个查询配置
2075
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3482
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
2076
3483
 
2077
3484
  **返回值:**
2078
3485
 
@@ -2101,7 +3508,7 @@ function useListStreamQuery<
2101
3508
  **参数:**
2102
3509
 
2103
3510
  - `options`: 包含 initialQuery 和 listStream 函数的配置选项
2104
- - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 false
3511
+ - `autoExecute`: 是否在组件挂载时自动执行查询(默认为 true
2105
3512
 
2106
3513
  **返回值:**
2107
3514