Files
gongxue-base/apps/admin/src/hooks/useApiMutation.ts
wangziqi 9565a0f23c feat(admin): 深化 TanStack Query 数据层
- 新增 queryKeys.ts 统一 QueryKey 工厂(覆盖全部模块,invalidate/refetch/prefetch 同源)
- 新增 queryClient.ts 全局配置:retry 1、staleTime 30s、gcTime 5min、refetchOnWindowFocus false
- 新增 useApiQuery:zod schema 校验 + 类型收敛 + select 转换,消除页面重复 validateResponse 样板
- useApiMutation 支持 onMutate/onSettled/context(乐观更新)
- 示范迁移:Organizations(useApiQuery + 乐观更新归档/恢复)、Students(useApiQuery + queryKeys + 打开抽屉前 prefetch 档案聚合)
- StudentProfileContent queryKey 统一为 queryKeys.archive.detail,prefetch 可命中缓存
2026-08-08 18:37:55 +08:00

64 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useMutation, useQueryClient, type QueryKey } from '@tanstack/react-query';
import { message } from '../ui/app-message';
import { getErrorMessage } from '../utils/error';
interface UseApiMutationOptions<TData, TVars, TContext> {
/** 成功后自动失效的查询 key触发列表/详情刷新) */
invalidate?: QueryKey[];
/** 乐观更新mutate 前同步改缓存,返回回滚上下文(失败时传给 onError */
onMutate?: (vars: TVars) => Promise<TContext | undefined> | TContext | undefined;
/** 成功后回调(例如关闭弹窗) */
onSuccess?: (data: TData, vars: TVars, context?: TContext) => void;
/** 失败回调;提供时由调用方负责(含乐观更新回滚),否则默认用 getErrorMessage 弹错误提示 */
onError?: (error: unknown, vars: TVars, context?: TContext) => void;
/** 结束后回调(无论成败) */
onSettled?: (data: TData | undefined, error: unknown, vars: TVars, context?: TContext) => void;
}
/**
* useMutation 的轻量封装:统一错误提示 + 成功后 invalidateQueries
* 消除手写 `await api.xxx(); await fetchData();` 样板。
*
* 乐观更新示例:
* ```ts
* const mutation = useApiMutation(fn, {
* onMutate: async (vars) => {
* await queryClient.cancelQueries({ queryKey });
* const previous = queryClient.getQueryData(queryKey);
* queryClient.setQueryData(queryKey, updater);
* return previous; // 回滚上下文
* },
* onError: (_e, _v, previous) => queryClient.setQueryData(queryKey, previous),
* });
* ```
*/
export function useApiMutation<TData = unknown, TVars = void, TContext = unknown>(
mutationFn: (vars: TVars) => Promise<TData>,
options: UseApiMutationOptions<TData, TVars, TContext> = {},
) {
const queryClient = useQueryClient();
return useMutation<TData, Error, TVars, TContext>({
mutationFn,
// 包装 onMutate允许调用方返回 undefined无回滚上下文
// React Query 的 onMutate 类型要求返回 TContext
onMutate: async (vars) => {
const context = await options.onMutate?.(vars);
return context as TContext;
},
onSuccess: (data, vars, context) => {
for (const key of options.invalidate ?? []) {
void queryClient.invalidateQueries({ queryKey: key });
}
options.onSuccess?.(data, vars, context);
},
onError: (error, vars, context) => {
if (options.onError) {
options.onError(error, vars, context);
} else {
message.error(getErrorMessage(error));
}
},
onSettled: options.onSettled,
});
}