- 新增 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 可命中缓存
64 lines
2.5 KiB
TypeScript
64 lines
2.5 KiB
TypeScript
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,
|
||
});
|
||
}
|