# @airma/react-hooks > @airma/react-hooks 是 @airma/react-state(同步状态管理)和 @airma/react-effect(异步状态管理)的整合包。它统一了两者的 provide、Provider 和 ConfigProvider,并额外提供一组实用 hooks。同时使用两个包时,推荐通过本包引入 API,以避免引用冲突。 - 版本: 18.6.3 - 许可证: MIT - 仓库: https://github.com/filefoxper/airma - 主页: https://filefoxper.github.io/airma/#/react-hooks/index - npm: https://www.npmjs.com/package/@airma/react-hooks ## 安装 ``` npm i @airma/react-hooks ``` 依赖: React >=16.8.0, @airma/react-state >=18.6.3, @airma/react-effect >=18.6.1 浏览器支持: Chrome >=91, Edge >=91, Firefox >=90, Safari >=15 ## 为什么使用本包 @airma/react-state 和 @airma/react-effect 的 Provider/provide 系统本就互通(会话键是特殊的模型键),但分别导入时会出现两套 provide/Provider/ConfigProvider。本包将它们统一为一个,用法更简洁: ```ts // 推荐:从整合包导入 import { provide, useModel, useQuery, Strategy, ConfigProvider } from '@airma/react-hooks'; // 不推荐:分别从子包导入(容易混淆两套 provide/Provider/ConfigProvider) import { provide, useModel } from '@airma/react-state'; import { provide as sessionProvide, useQuery, Strategy } from '@airma/react-effect'; ``` ## 统一 API ### provide ```ts function provide(...keys): { (component: ComponentType): typeof component; to: (component: ComponentType) => typeof component; }; ``` 统一的高阶组件 API,支持同时传入模型键和会话键,为子组件创建 Provider 包装。支持 `provide(keys).to(Component)` 和 `provide(keys)(Component)` 两种调用方式。 ```ts import { model, provide, session } from '@airma/react-hooks'; const countKey = model(counting).createKey(0); const queryKey = session(fetchUsers, 'query').createKey(); // 模型键和会话键混合使用 const App = provide(countKey, queryKey).to(() => { // 子组件可通过各自的键访问对应的库 return
...
; }); ``` ### Provider ```ts const Provider: FC<{ value: Array> | Record; children?: ReactNode; }>; ``` 统一的 Provider 组件,与 provide 功能相同,适用于 JSX 中直接使用。 ```ts import { Provider } from '@airma/react-hooks'; ``` ### ConfigProvider ```ts type GlobalConfig = { batchUpdate?: (callback: () => void) => void; strategy?: ( strategies: (StrategyType | null | undefined)[], type: 'query' | 'mutation' ) => (StrategyType | null | undefined)[]; }; const ConfigProvider: FC<{ value: GlobalConfig; children?: ReactNode; }>; ``` 统一的全局配置组件,合并了 @airma/react-state 和 @airma/react-effect 的配置: - `batchUpdate` — React <18 时配置 `unstable_batchedUpdates` 优化渲染(同时作用于同步和异步状态管理) - `strategy` — 公共策略链组合函数,为所有会话注入公共策略(如全局错误处理) ## 来自 @airma/react-state 的 API 以下 API 从 @airma/react-state 原样导出,详细用法请参考 @airma/react-state 文档: - `model` — 模型声明 API,支持 createKey/createStore/createField/createMethod/produce 等流式调用 - `createKey` — 创建模型键 - `createStore` — 创建模型静态库 - `useModel` — 创建或连接模型实例 - `useSignal` — 创建信号函数,支持高性能选择性渲染 - `useControlledModel` — 受控模型,状态完全受外部控制 - `useSelector` — 从库中选取/重组实例字段,仅在选取结果变化时重渲染 - `shallowEqual` — 浅对比函数,常用于 useSelector 的 equality 参数 ## 来自 @airma/react-effect 的 API 以下 API 从 @airma/react-effect 原样导出,详细用法请参考 @airma/react-effect 文档: - `session` — 会话声明 API,支持 createKey/createStore/useQuery/useMutation 等流式调用 - `createSessionKey` — 创建会话键 - `createSessionStore` — 创建会话静态库 - `useQuery` — 创建查询会话,默认支持加载、依赖更新、人工触发 - `useMutation` — 创建修改会话,默认仅支持人工触发 - `useSession` — 订阅库的会话状态变更 - `useLoadedSession` — 同 useSession,但 data 类型为 T(确认已加载时使用) - `useResponse` — 监听会话执行完毕后的回调(含 useSuccess/useFailure 子 API) - `useIsFetching` — 检测是否有会话正在执行 - `useLazyComponent` — 监听会话加载状态,异步加载组件 - `Strategy` — 内置策略集合(debounce/cache/memo/validate/once/atomic/reduce/success/failure/response 等) ## 来自 @airma/react-hooks-core 的实用 API ### usePersistFn ```ts function usePersistFn any>(callback: T): T; ``` 持久化函数引用。内容随渲染更新,但引用始终不变。可替代 `useCallback`,无需声明依赖。 ```ts import { usePersistFn } from '@airma/react-hooks'; const call = usePersistFn((v: string) => { /* 最新逻辑 */ }); // call 引用不变,可安全传给 memo 组件 ``` ### useMount ```ts function useMount(callback: () => (() => void) | void): void; ``` 相当于 `useEffect(callback, [])`,仅在组件挂载时执行。 ### useUnmount ```ts function useUnmount(destroy: () => void): void; ``` 相当于 `useEffect(() => destroy, [])`,仅在组件卸载时执行。 ### useUpdate ```ts function useUpdate( callback: (prevDeps: [...T]) => (() => void) | void, deps?: [...T] ): void; ``` 监听依赖变化(跳过首次挂载),回调接收变化前的依赖值数组。 ```ts import { useUpdate } from '@airma/react-hooks'; useUpdate((prevDeps) => { const [prevValue] = prevDeps; console.log('changed from', prevValue, 'to', value); }, [value]); ``` ### useRefresh ```ts function useRefresh any>( method: T, variables: Parameters | { refreshDeps?: any[]; variables: Parameters } ): void; ``` 通过依赖 variables 产生的副作用调用 method。设置 refreshDeps 后,以 refreshDeps 替代 variables 作副作用依赖。 ### useDebounceFn ```ts function useDebounceFn any>( fn: F, option: number | { lead?: boolean; ms: number } ): (...args: Parameters) => Promise>; ``` 将函数包装为防抖异步函数。option.lead 为 true 时先执行后防抖,否则先防抖后执行。 ### shallowEqual ```ts function shallowEqual(prev: R, current: R): boolean; ``` 浅对比两个对象是否等价。 ## 使用模式 ### 同步 + 异步混合使用 模型管理查询条件状态,模型实例中的数据作为 useQuery 的 variables 依赖,条件变化时自动触发查询: ```ts import { model, session, provide } from '@airma/react-hooks'; const queryFormKey = model(function queryForm(state: { name: string; role: string }) { return { ...state, changeName: (name: string) => ({ ...state, name }), changeRole: (role: string) => ({ ...state, role }) }; }).createKey({ name: '', role: '' }); // fetchUsers 接收原始值参数,variables 中均为原始值,无需额外设置 deps const userQueryKey = session(fetchUsers, 'query').createKey(); const QueryForm = () => { const { name, role, changeName, changeRole } = queryFormKey.useModel(); return (
changeName(e.target.value)} />
); }; const UserList = () => { const [{ data, isFetching }] = userQueryKey.useSession(); if (isFetching) return
Loading...
; return
    {data?.map(u =>
  • {u.name}
  • )}
; }; const App = provide(queryFormKey, userQueryKey).to(() => { const { name, role } = queryFormKey.useModel(); // 模型状态作为查询依赖,条件变化时自动触发查询 userQueryKey.useQuery([name, role]); return (
); }); ``` ### 全局配置示例 通过 ConfigProvider 的 strategy 配置函数,可以在所有 useQuery/useMutation 的运行时策略链中注入公共策略。参数 `s` 是当前会话自身的策略数组,返回值是最终的策略链。 ```ts import { ConfigProvider, Strategy } from '@airma/react-hooks'; const globalConfig = { // Strategy.failure 放在第一条(链首),起到异常兜底作用: // 当所有后续策略都未处理异常时,异常最终会传递到这里被捕获 strategy: (s, type) => [ Strategy.failure((e) => { message.error(e.message); }), ...s, type === 'query' ? Strategy.memo() : null ] }; const Root = () => ( ); ``` ## 常见陷阱 ### variables 中包含对象时需使用 deps useQuery 默认行为类似 `React.useEffect`,以 variables 作为副作用依赖项做浅比较。如果 variables 中包含每次渲染都会创建的新对象,会导致 useQuery 不断执行。 错误示范: ```ts const { name, role } = queryFormKey.useModel(); // 每次渲染都会创建新的 { name, role } 对象引用, // 浅比较始终认为依赖发生变化,导致无限执行 userQueryKey.useQuery([{ name, role }]); ``` 正确做法一:让异步函数接收原始值参数,variables 中自然都是原始值 ```ts // fetchUsers(name: string, role: string) userQueryKey.useQuery([name, role]); ``` 正确做法二:如果异步函数必须接收对象参数,使用 deps 指定稳定的原始值作为依赖 ```ts // fetchUsers(query: { name: string, role: string }) userQueryKey.useQuery({ variables: [{ name, role }], deps: [name, role] }); ``` ## 最佳实践 - 同时使用 @airma/react-state 和 @airma/react-effect 时,统一从 @airma/react-hooks 导入 - 对会话库推荐只设一个工作者(useQuery/useMutation),其他组件通过 useSession 订阅和触发 - 会话状态 sessionState 本身就是 state,避免对 sessionState.data 做不必要的 setState - 使用 trigger() 触发时,确保 useQuery/useMutation 已配置 variables - 处理异步操作推荐使用 @airma/react-effect,而非 @airma/react-state 的 produce