@ahoo-wang/fetcher-react 3.1.9 → 3.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.zh-CN.md CHANGED
@@ -37,12 +37,13 @@
37
37
  - [useLatest Hook](#uselatest-hook)
38
38
  - [useRefs Hook](#userefs-hook)
39
39
  - [useKeyStorage Hook](#usekeystorage-hook)
40
+ - [useImmerKeyStorage Hook](#useimmerkeystorage-hook)
40
41
  - [Wow 查询 Hooks](#wow-查询-hooks)
41
- - [useListQuery Hook](#uselistquery-hook)
42
- - [usePagedQuery Hook](#usepagedquery-hook)
43
- - [useSingleQuery Hook](#usesinglequery-hook)
44
- - [useCountQuery Hook](#usecountquery-hook)
45
- - [useListStreamQuery Hook](#useliststreamquery-hook)
42
+ - [useListQuery Hook](#uselistquery-hook)
43
+ - [usePagedQuery Hook](#usepagedquery-hook)
44
+ - [useSingleQuery Hook](#usesinglequery-hook)
45
+ - [useCountQuery Hook](#usecountquery-hook)
46
+ - [useListStreamQuery Hook](#useliststreamquery-hook)
46
47
  - [最佳实践](#最佳实践)
47
48
  - [API 参考](#api-参考)
48
49
  - [许可证](#许可证)
@@ -98,443 +99,262 @@ const MyComponent = () => {
98
99
  };
99
100
  ```
100
101
 
101
- #### 自动执行示例
102
+ ### useImmerKeyStorage Hook
102
103
 
103
- ```typescript jsx
104
- import { useListQuery } from '@ahoo-wang/fetcher-react';
105
-
106
- const MyComponent = () => {
107
- const { result, loading, error, execute, setCondition } = useListQuery({
108
- initialQuery: { condition: {}, projection: {}, sort: [], limit: 10 },
109
- execute: async (listQuery) => fetchListData(listQuery),
110
- autoExecute: true, // 在组件挂载时自动执行
111
- });
112
-
113
- // 查询将在组件挂载时自动执行
114
- // 您仍然可以通过 execute() 手动触发或更新条件
104
+ 🚀 **Immer 驱动的不可变状态管理** - `useImmerKeyStorage` hook 通过集成 Immer 的 `produce` 函数扩展了 `useKeyStorage`,允许开发者以直观的"可变"方式更新存储值,同时在底层保持不可变性。非常适合复杂对象的操作,具有自动存储同步功能。
115
105
 
116
- if (loading) return <div>加载中...</div>;
117
- if (error) return <div>错误: {error.message}</div>;
118
-
119
- return (
120
- <div>
121
- <ul>
122
- {result?.map((item, index) => (
123
- <li key={index}>{item.name}</li>
124
- ))}
125
- </ul>
126
- </div>
127
- );
128
- };
129
- ```
106
+ #### 主要优势
130
107
 
131
- ### 防抖 Hooks
108
+ - **直观的变更语法**: 编写看起来可变的代码,但产生不可变更新
109
+ - **深度对象支持**: 轻松处理嵌套对象和数组
110
+ - **类型安全**: 完整的 TypeScript 支持和编译时错误检查
111
+ - **性能优化**: 利用 Immer 的结构共享和最小化重渲染
112
+ - **自动同步**: 变更自动持久化到存储并跨组件同步
132
113
 
133
- 🚀 **高级 React 防抖库** - 强大的 hooks 将防抖与异步操作相结合,为 API 调用、用户交互和 Promise 执行提供无缝的速率限制。
114
+ #### 使用场景
134
115
 
135
- #### useDebouncedCallback
116
+ 在需要以下情况时选择 `useImmerKeyStorage` 而不是 `useKeyStorage`:
136
117
 
137
- 一个 React hook,为任何回调函数提供防抖版本,支持前缘/后缘执行选项。
118
+ - 更新嵌套对象属性
119
+ - 执行复杂的数组操作(push、splice 等)
120
+ - 原子性地进行多个相关变更
121
+ - 处理深度嵌套的数据结构
138
122
 
139
123
  ```typescript jsx
140
- import { useDebouncedCallback } from '@ahoo-wang/fetcher-react';
141
-
142
- const SearchComponent = () => {
143
- const { run: debouncedSearch, cancel, isPending } = useDebouncedCallback(
144
- async (query: string) => {
145
- const response = await fetch(`/api/search?q=${query}`);
146
- const results = await response.json();
147
- console.log('搜索结果:', results);
148
- },
149
- { delay: 300 }
150
- );
124
+ import { KeyStorage } from '@ahoo-wang/fetcher-storage';
125
+ import { useImmerKeyStorage } from '@ahoo-wang/fetcher-react';
151
126
 
152
- const handleSearch = (query: string) => {
153
- if (query.trim()) {
154
- debouncedSearch(query);
155
- } else {
156
- cancel(); // 取消任何待处理的搜索
157
- }
158
- };
127
+ const MyComponent = () => {
128
+ const prefsStorage = new KeyStorage<{
129
+ theme: string;
130
+ volume: number;
131
+ notifications: boolean;
132
+ shortcuts: { [key: string]: string };
133
+ }>({
134
+ key: 'user-prefs'
135
+ });
136
+
137
+ // 不使用默认值 - 可能为 null
138
+ const [prefs, updatePrefs, clearPrefs] = useImmerKeyStorage(prefsStorage);
159
139
 
160
140
  return (
161
141
  <div>
162
- <input
163
- type="text"
164
- placeholder="搜索..."
165
- onChange={(e) => handleSearch(e.target.value)}
166
- />
167
- {isPending() && <div>搜索中...</div>}
142
+ <p>主题: {prefs?.theme || '默认'}</p>
143
+ <button onClick={() => updatePrefs(draft => { draft.theme = 'dark'; })}>
144
+ 切换到深色主题
145
+ </button>
146
+ <button onClick={() => updatePrefs(draft => { draft.volume += 10; })}>
147
+ 增加音量
148
+ </button>
149
+ <button onClick={clearPrefs}>
150
+ 清除偏好设置
151
+ </button>
168
152
  </div>
169
153
  );
170
154
  };
171
155
  ```
172
156
 
173
- **配置选项:**
174
-
175
- - `delay`: 执行前的延迟毫秒数(必需,正数)
176
- - `leading`: 第一次调用时立即执行(默认: false)
177
- - `trailing`: 最后一次调用后延迟执行(默认: true)
178
-
179
- #### useDebouncedExecutePromise
180
-
181
- 将 Promise 执行与防抖功能相结合,非常适合 API 调用和异步操作。
157
+ #### 使用默认值
182
158
 
183
159
  ```typescript jsx
184
- import { useDebouncedExecutePromise } from '@ahoo-wang/fetcher-react';
185
-
186
- const DataFetcher = () => {
187
- const { loading, result, error, run } = useDebouncedExecutePromise({
188
- debounce: { delay: 300 },
160
+ const AudioControls = () => {
161
+ const settingsStorage = new KeyStorage<{ volume: number; muted: boolean }>({
162
+ key: 'audio-settings'
189
163
  });
190
164
 
191
- const handleLoadUser = (userId: string) => {
192
- run(async () => {
193
- const response = await fetch(`/api/users/${userId}`);
194
- return response.json();
195
- });
196
- };
165
+ // 使用默认值 - 保证不为 null
166
+ const [settings, updateSettings, resetSettings] = useImmerKeyStorage(
167
+ settingsStorage,
168
+ { volume: 50, muted: false }
169
+ );
197
170
 
198
171
  return (
199
172
  <div>
200
- <button onClick={() => handleLoadUser('user123')}>
201
- 加载用户
173
+ <p>音量: {settings.volume}%</p>
174
+ <button onClick={() => updateSettings(draft => {
175
+ draft.volume = Math.min(100, draft.volume + 10);
176
+ draft.muted = false;
177
+ })}>
178
+ 增加音量
179
+ </button>
180
+ <button onClick={() => updateSettings(draft => { draft.muted = !draft.muted; })}>
181
+ 切换静音
182
+ </button>
183
+ <button onClick={resetSettings}>
184
+ 重置为默认值
202
185
  </button>
203
- {loading && <div>加载中...</div>}
204
- {error && <div>错误: {error.message}</div>}
205
- {result && <div>用户: {result.name}</div>}
206
186
  </div>
207
187
  );
208
188
  };
209
189
  ```
210
190
 
211
- #### useDebouncedFetcher
191
+ #### 高级用法模式
212
192
 
213
- 专门的 hook,将 HTTP 获取与防抖相结合,建立在核心获取器库之上。
193
+ ##### 批量更新
214
194
 
215
195
  ```typescript jsx
216
- import { useDebouncedFetcher } from '@ahoo-wang/fetcher-react';
217
-
218
- const SearchInput = () => {
219
- const [query, setQuery] = useState('');
220
- const { loading, result, error, run } = useDebouncedFetcher({
221
- debounce: { delay: 300 },
222
- onSuccess: (data) => {
223
- setSearchResults(data.results);
224
- }
196
+ const updateUserProfile = () => {
197
+ updatePrefs(draft => {
198
+ draft.theme = 'dark';
199
+ draft.notifications = true;
200
+ draft.volume = 75;
225
201
  });
226
-
227
- const handleChange = (value: string) => {
228
- setQuery(value);
229
- if (value.trim()) {
230
- run({
231
- url: '/api/search',
232
- method: 'GET',
233
- params: { q: value }
234
- });
235
- }
236
- };
237
-
238
- return (
239
- <div>
240
- <input
241
- value={query}
242
- onChange={(e) => handleChange(e.target.value)}
243
- placeholder="搜索..."
244
- />
245
- {loading && <div>搜索中...</div>}
246
- {error && <div>错误: {error.message}</div>}
247
- {result && <SearchResults data={result} />}
248
- </div>
249
- );
250
202
  };
251
203
  ```
252
204
 
253
- **防抖策略:**
254
-
255
- - **前缘执行**: 第一次调用时立即执行,然后对后续调用进行防抖
256
- - **后缘执行**: 最后一次调用后延迟执行(默认行为)
257
- - **前缘 + 后缘**: 立即执行,如果再次调用则在延迟后再次执行
258
-
259
- ### useExecutePromise Hook
260
-
261
- `useExecutePromise` hook 管理异步操作,具有自动状态处理、竞态条件保护和 promise 状态选项支持。
205
+ ##### 数组操作
262
206
 
263
207
  ```typescript jsx
264
- import { useExecutePromise } from '@ahoo-wang/fetcher-react';
265
-
266
- const MyComponent = () => {
267
- const { loading, result, error, execute, reset } = useExecutePromise<string>();
208
+ const todoStorage = new KeyStorage<{
209
+ todos: Array<{ id: number; text: string; done: boolean }>;
210
+ }>({
211
+ key: 'todos',
212
+ });
268
213
 
269
- const fetchData = async () => {
270
- const response = await fetch('/api/data');
271
- return response.text();
272
- };
214
+ const [state, updateState] = useImmerKeyStorage(todoStorage, { todos: [] });
273
215
 
274
- const handleFetch = () => {
275
- execute(fetchData); // 使用 promise supplier
276
- };
216
+ // 添加新待办事项
217
+ const addTodo = (text: string) => {
218
+ updateState(draft => {
219
+ draft.todos.push({
220
+ id: Date.now(),
221
+ text,
222
+ done: false,
223
+ });
224
+ });
225
+ };
277
226
 
278
- const handleDirectPromise = () => {
279
- const promise = fetch('/api/data').then(res => res.text());
280
- execute(promise); // 使用直接 promise
281
- };
227
+ // 切换待办事项状态
228
+ const toggleTodo = (id: number) => {
229
+ updateState(draft => {
230
+ const todo = draft.todos.find(t => t.id === id);
231
+ if (todo) {
232
+ todo.done = !todo.done;
233
+ }
234
+ });
235
+ };
282
236
 
283
- if (loading) return <div>加载中...</div>;
284
- if (error) return <div>错误: {error.message}</div>;
285
- return (
286
- <div>
287
- <button onClick={handleFetch}>使用 Supplier 获取</button>
288
- <button onClick={handleDirectPromise}>使用 Promise 获取</button>
289
- <button onClick={reset}>重置</button>
290
- {result && <p>{result}</p>}
291
- </div>
292
- );
237
+ // 清除已完成的待办事项
238
+ const clearCompleted = () => {
239
+ updateState(draft => {
240
+ draft.todos = draft.todos.filter(todo => !todo.done);
241
+ });
293
242
  };
294
243
  ```
295
244
 
296
- ### usePromiseState Hook
297
-
298
- `usePromiseState` hook 提供 promise 操作的状态管理,无执行逻辑。支持静态选项和动态选项供应商。
245
+ ##### 嵌套对象更新
299
246
 
300
247
  ```typescript jsx
301
- import { usePromiseState, PromiseStatus } from '@ahoo-wang/fetcher-react';
302
-
303
- const MyComponent = () => {
304
- const { status, loading, result, error, setSuccess, setError, setIdle } = usePromiseState<string>();
248
+ const configStorage = new KeyStorage<{
249
+ ui: { theme: string; language: string };
250
+ features: { [key: string]: boolean };
251
+ }>({
252
+ key: 'app-config',
253
+ });
305
254
 
306
- const handleSuccess = () => setSuccess('数据加载成功');
307
- const handleError = () => setError(new Error('加载失败'));
255
+ const [config, updateConfig] = useImmerKeyStorage(configStorage, {
256
+ ui: { theme: 'light', language: 'zh' },
257
+ features: {},
258
+ });
308
259
 
309
- return (
310
- <div>
311
- <button onClick={handleSuccess}>设置成功</button>
312
- <button onClick={handleError}>设置错误</button>
313
- <button onClick={setIdle}>重置</button>
314
- <p>状态: {status}</p>
315
- {loading && <p>加载中...</p>}
316
- {result && <p>结果: {result}</p>}
317
- {error && <p>错误: {error.message}</p>}
318
- </div>
319
- );
260
+ // 更新嵌套属性
261
+ const updateTheme = (theme: string) => {
262
+ updateConfig(draft => {
263
+ draft.ui.theme = theme;
264
+ });
320
265
  };
321
- ```
322
-
323
- #### usePromiseState with Options Supplier
324
-
325
- ```typescript jsx
326
- import { usePromiseState, PromiseStatus } from '@ahoo-wang/fetcher-react';
327
266
 
328
- const MyComponent = () => {
329
- // 使用选项供应商进行动态配置
330
- const optionsSupplier = () => ({
331
- initialStatus: PromiseStatus.IDLE,
332
- onSuccess: async (result: string) => {
333
- await saveToAnalytics(result);
334
- console.log('成功:', result);
335
- },
336
- onError: async (error) => {
337
- await logErrorToServer(error);
338
- console.error('错误:', error);
339
- },
267
+ const toggleFeature = (feature: string) => {
268
+ updateConfig(draft => {
269
+ draft.features[feature] = !draft.features[feature];
340
270
  });
341
-
342
- const { setSuccess, setError } = usePromiseState<string>(optionsSupplier);
343
-
344
- return (
345
- <div>
346
- <button onClick={() => setSuccess('动态成功!')}>设置成功</button>
347
- <button onClick={() => setError(new Error('动态错误!'))}>设置错误</button>
348
- </div>
349
- );
350
271
  };
351
272
  ```
352
273
 
353
- ### useRequestId Hook
354
-
355
- `useRequestId` hook 提供请求ID管理,用于防止异步操作中的竞态条件。
274
+ ##### 带验证的条件更新
356
275
 
357
276
  ```typescript jsx
358
- import { useRequestId } from '@ahoo-wang/fetcher-react';
359
-
360
- const MyComponent = () => {
361
- const { generate, isLatest, invalidate } = useRequestId();
362
-
363
- const handleFetch = async () => {
364
- const requestId = generate();
365
-
366
- try {
367
- const result = await fetchData();
368
-
369
- if (isLatest(requestId)) {
370
- setData(result);
371
- }
372
- } catch (error) {
373
- if (isLatest(requestId)) {
374
- setError(error);
375
- }
277
+ const updateVolume = (newVolume: number) => {
278
+ updateSettings(draft => {
279
+ if (newVolume >= 0 && newVolume <= 100) {
280
+ draft.volume = newVolume;
281
+ draft.muted = false; // 音量改变时取消静音
376
282
  }
377
- };
378
-
379
- return (
380
- <div>
381
- <button onClick={handleFetch}>获取数据</button>
382
- <button onClick={invalidate}>取消进行中</button>
383
- </div>
384
- );
283
+ });
385
284
  };
386
285
  ```
387
286
 
388
- ### useLatest Hook
389
-
390
- `useLatest` hook 返回包含最新值的 ref 对象,用于在异步回调中访问当前值。
287
+ ##### 返回新值
391
288
 
392
289
  ```typescript jsx
393
- import { useLatest } from '@ahoo-wang/fetcher-react';
394
-
395
- const MyComponent = () => {
396
- const [count, setCount] = useState(0);
397
- const latestCount = useLatest(count);
398
-
399
- const handleAsync = async () => {
400
- await someAsyncOperation();
401
- console.log('最新计数:', latestCount.current); // 始终是最新值
402
- };
290
+ // 替换整个状态
291
+ const resetToFactorySettings = () => {
292
+ updateSettings(() => ({ volume: 50, muted: false }));
293
+ };
403
294
 
404
- return (
405
- <div>
406
- <p>计数: {count}</p>
407
- <button onClick={() => setCount(c => c + 1)}>递增</button>
408
- <button onClick={handleAsync}>异步记录</button>
409
- </div>
410
- );
295
+ // 计算更新
296
+ const setMaxVolume = () => {
297
+ updateSettings(draft => ({ ...draft, volume: 100, muted: false }));
411
298
  };
412
299
  ```
413
300
 
414
- ### useRefs Hook
415
-
416
- `useRefs` hook 提供 Map-like 接口用于动态管理多个 React refs。它允许通过键注册、检索和管理 refs,并在组件卸载时自动清理。
301
+ ##### 错误处理
417
302
 
418
303
  ```typescript jsx
419
- import { useRefs } from '@ahoo-wang/fetcher-react';
420
-
421
- const MyComponent = () => {
422
- const refs = useRefs<HTMLDivElement>();
423
-
424
- const handleFocus = (key: string) => {
425
- const element = refs.get(key);
426
- element?.focus();
427
- };
428
-
429
- return (
430
- <div>
431
- <div ref={refs.register('first')} tabIndex={0}>第一个元素</div>
432
- <div ref={refs.register('second')} tabIndex={0}>第二个元素</div>
433
- <button onClick={() => handleFocus('first')}>聚焦第一个</button>
434
- <button onClick={() => handleFocus('second')}>聚焦第二个</button>
435
- </div>
436
- );
304
+ const safeUpdate = (updater: (draft: any) => void) => {
305
+ try {
306
+ updatePrefs(updater);
307
+ } catch (error) {
308
+ console.error('更新偏好设置失败:', error);
309
+ // 适当处理错误
310
+ }
437
311
  };
438
312
  ```
439
313
 
440
- 关键特性:
314
+ #### 最佳实践
441
315
 
442
- - **动态注册**: 使用字符串、数字或符号键注册 refs
443
- - **Map-like API**: 完整的 Map 接口,包括 get、set、has、delete 等
444
- - **自动清理**: 组件卸载时自动清空 refs
445
- - **类型安全**: 完整的 TypeScript 支持
316
+ ##### 推荐做法
446
317
 
447
- ### useKeyStorage Hook
318
+ - 用于复杂对象更新和数组操作
319
+ - 利用 Immer 的 draft 变更编写可读代码
320
+ - 在单个更新调用中组合多个相关变更
321
+ - 对保证非空状态使用默认值
322
+ - 在更新函数中适当处理错误
448
323
 
449
- `useKeyStorage` hook 为 KeyStorage 实例提供反应式状态管理。它订阅存储变化并返回当前值以及设置函数。可选接受默认值以在存储为空时使用。
324
+ ##### 避免做法
450
325
 
451
- ```typescript jsx
452
- import { KeyStorage } from '@ahoo-wang/fetcher-storage';
453
- import { useKeyStorage } from '@ahoo-wang/fetcher-react';
454
-
455
- const MyComponent = () => {
456
- const keyStorage = new KeyStorage<string>({ key: 'my-key' });
326
+ - 不要直接用赋值修改 draft 参数(`draft = newValue`)
327
+ - 不要在更新函数中执行副作用
328
+ - 不要依赖对象比较的引用相等性
329
+ - 不要用于简单的原始值更新(应使用 `useKeyStorage`)
457
330
 
458
- // 不使用默认值 - 可能为 null
459
- const [value, setValue] = useKeyStorage(keyStorage);
331
+ ##### 性能提示
460
332
 
461
- return (
462
- <div>
463
- <p>当前值: {value || '未存储值'}</p>
464
- <button onClick={() => setValue('新值')}>
465
- 更新值
466
- </button>
467
- </div>
468
- );
469
- };
470
- ```
333
+ - 将相关更新批量处理以最小化存储操作
334
+ - 当新状态依赖于之前状态时使用函数式更新
335
+ - 如果更新函数经常重新创建,考虑使用 `useCallback`
336
+ - 如果处理非常大的对象,请分析更新性能
471
337
 
472
- #### 使用默认值
338
+ ##### TypeScript 集成
473
339
 
474
340
  ```typescript jsx
475
- const MyComponent = () => {
476
- const keyStorage = new KeyStorage<string>({ key: 'theme' });
477
-
478
- // 使用默认值 - 保证不为 null
479
- const [theme, setTheme] = useKeyStorage(keyStorage, 'light');
480
-
481
- return (
482
- <div className={theme}>
483
- <p>当前主题: {theme}</p>
484
- <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
485
- 切换主题
486
- </button>
487
- </div>
488
- );
341
+ // 为更好的安全性定义严格类型
342
+ type UserPreferences = {
343
+ theme: 'light' | 'dark' | 'auto';
344
+ volume: number; // 0-100
345
+ notifications: boolean;
346
+ shortcuts: Record<string, string>;
489
347
  };
490
- ```
491
-
492
- ### 更多示例
493
-
494
- ```typescript jsx
495
- // 处理不同类型的值
496
- const numberStorage = new KeyStorage<number>({ key: 'counter' });
497
- const [count, setCount] = useKeyStorage(numberStorage, 0); // 默认为 0
498
-
499
- // 处理对象
500
- interface User {
501
- id: string;
502
- name: string;
503
- }
504
348
 
505
- const userStorage = new KeyStorage<User>({ key: 'current-user' });
506
- const [user, setUser] = useKeyStorage(userStorage, { id: '', name: '访客' });
507
-
508
- // 复杂状态管理
509
- const settingsStorage = new KeyStorage<{ volume: number; muted: boolean }>({
510
- key: 'audio-settings',
511
- });
512
- const [settings, setSettings] = useKeyStorage(settingsStorage, {
513
- volume: 50,
514
- muted: false,
349
+ const prefsStorage = new KeyStorage<UserPreferences>({
350
+ key: 'user-prefs',
515
351
  });
516
352
 
517
- // 更新特定属性
518
- const updateVolume = (newVolume: number) => {
519
- setSettings({ ...settings, volume: newVolume });
520
- };
521
- ```
522
-
523
- ### 更多示例
353
+ // TypeScript 将捕获无效更新
354
+ const [prefs, updatePrefs] = useImmerKeyStorage(prefsStorage);
524
355
 
525
- ```typescript jsx
526
- // 处理不同类型的值
527
- const numberStorage = new KeyStorage<number>({ key: 'counter' });
528
- const [count, setCount] = useKeyStorage(numberStorage);
529
-
530
- // 处理对象
531
- interface User {
532
- id: string;
533
- name: string;
534
- }
535
-
536
- const userStorage = new KeyStorage<User>({ key: 'current-user' });
537
- const [user, setUser] = useKeyStorage(userStorage);
356
+ // 这将导致 TypeScript 错误:
357
+ // updatePrefs(draft => { draft.theme = 'invalid'; });
538
358
  ```
539
359
 
540
360
  ## Wow 查询 Hooks
@@ -1057,6 +877,73 @@ function useKeyStorage<T>(
1057
877
 
1058
878
  - 包含当前存储值和更新函数的元组
1059
879
 
880
+ ### useImmerKeyStorage
881
+
882
+ ```typescript
883
+ // 不使用默认值 - 可能返回 null
884
+ function useImmerKeyStorage<T>(
885
+ keyStorage: KeyStorage<T>,
886
+ ): [
887
+ T | null,
888
+ (updater: (draft: T | null) => T | null | void) => void,
889
+ () => void,
890
+ ];
891
+
892
+ // 使用默认值 - 保证非空
893
+ function useImmerKeyStorage<T>(
894
+ keyStorage: KeyStorage<T>,
895
+ defaultValue: T,
896
+ ): [T, (updater: (draft: T) => T | null | void) => void, () => void];
897
+ ```
898
+
899
+ 为 KeyStorage 实例提供 Immer 驱动的不可变状态管理的 React hook。通过集成 Immer 的 `produce` 函数扩展 `useKeyStorage`,允许直观的"可变"更新存储值,同时保持不可变性。
900
+
901
+ **类型参数:**
902
+
903
+ - `T`: 存储值的数据类型
904
+
905
+ **参数:**
906
+
907
+ - `keyStorage`: 要订阅和管理的 KeyStorage 实例。应该是稳定的引用(useRef、memo 或模块级实例)
908
+ - `defaultValue` _(可选)_: 当存储为空时使用的默认值。提供时,hook 保证返回的值永远不会为 null
909
+
910
+ **返回值:**
911
+
912
+ 包含以下元素的元组:
913
+
914
+ - **当前值**: `T | null`(无默认值时)或 `T`(有默认值时)
915
+ - **更新函数**: `(updater: (draft: T | null) => T | null | void) => void` - Immer 驱动的更新函数
916
+ - **清除函数**: `() => void` - 删除存储值的函数
917
+
918
+ **更新函数:**
919
+
920
+ 更新函数接收一个可以直接变更的 `draft` 参数。Immer 将从这些变更中产生不可变更新。更新函数也可以直接返回新值或 `null` 来清除存储。
921
+
922
+ **示例:**
923
+
924
+ ```typescript
925
+ // 基本对象更新
926
+ const [user, updateUser] = useImmerKeyStorage(userStorage);
927
+ updateUser(draft => {
928
+ if (draft) {
929
+ draft.name = 'John';
930
+ draft.age = 30;
931
+ }
932
+ });
933
+
934
+ // 数组操作
935
+ const [todos, updateTodos] = useImmerKeyStorage(todosStorage, []);
936
+ updateTodos(draft => {
937
+ draft.push({ id: 1, text: '新待办事项', done: false });
938
+ });
939
+
940
+ // 返回新值
941
+ updateTodos(() => [{ id: 1, text: '重置待办事项', done: false }]);
942
+
943
+ // 清除存储
944
+ updateTodos(() => null);
945
+ ```
946
+
1060
947
  ### useListQuery
1061
948
 
1062
949
  ```typescript