如何让TypeScript基于queryFn返回类型自动推导TanStack useQuery的TError类型?
如何让TypeScript基于queryFn返回类型自动推导TanStack useQuery的TError类型?
我完全懂你的困扰——TanStack Query的useQuery能自动从queryFn的resolve结果推导TData类型,但错误类型TError却总是要手动指定,确实有点麻烦。核心原因其实是TypeScript本身不会自动跟踪Promise的reject类型,而useQuery默认把TError设为unknown,所以没法自动关联到你的IProblemDetails。
下面给你几个实用的解决方案,按复杂度从低到高排序:
方案1:直接显式指定泛型参数
这是最直接的方式,调用useQuery时手动传入TData和TError的类型:
// 假设你的资源数据类型是MyResource const { data, error } = useQuery<MyResource, IProblemDetails>({ queryKey: ['resource', resourceId], queryFn: () => getData<MyResource>({ resourceId }), }); // 现在error的类型就是IProblemDetails | null,TypeScript会自动提示它的属性
这种方式简单直接,适合偶尔使用的场景,但如果每个请求都要写一遍IProblemDetails会有点重复。
方案2:封装自定义useQuery钩子(推荐)
如果你的所有API请求错误都遵循IProblemDetails规范,那可以封装一个自己的useApiQuery钩子,默认把TError绑定为IProblemDetails,这样就不用每次都手动指定了:
import { useQuery, UseQueryOptions, UseQueryResult } from '@tanstack/react-query'; // 自定义钩子,默认错误类型为IProblemDetails export function useApiQuery< TQueryFnData = unknown, TError = IProblemDetails, TData = TQueryFnData, TQueryKey extends readonly any[] = any[] >( options: Omit<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, 'queryFn'> & { queryFn: () => Promise<TQueryFnData>; } ): UseQueryResult<TData, TError> { return useQuery(options); } // 使用的时候,只需要指定数据类型,错误类型自动推导 const { data, error } = useApiQuery({ queryKey: ['resource', resourceId], queryFn: () => getData<MyResource>({ resourceId }), }); // error的类型自动是IProblemDetails | null,完全不用额外配置
这个方案一劳永逸,适合项目中所有API错误类型统一的场景。
方案3:进阶自动推导(支持多错误类型)
如果你的项目里有不同的错误类型,想让钩子自动从queryFn的reject结果推导TError,可以通过自定义Promise类型和工具类型实现。不过这个方案稍微复杂一点,适合有特殊需求的场景:
首先,给你的Promise加上错误类型的标注:
// 自定义带错误类型的Promise类型 type PromiseWithError<T, E> = Promise<T> & { __errorType?: E }; // 修改getData的返回类型,明确标注错误类型 export async function getData<T>({resourceId}: {resourceId: string}): PromiseWithError<T, IProblemDetails> { const res = await new ApiWithAuth().get<T>(`api/resource/${resourceId}`); if (res.isError) { return Promise.reject(res.problem) as PromiseWithError<T, IProblemDetails>; } return res.data as PromiseWithError<T, IProblemDetails>; }
然后写一个工具类型提取错误类型,再封装钩子:
// 提取Promise的resolve类型 type PromiseResolve<T> = T extends Promise<infer R> ? R : never; // 提取自定义Promise的错误类型 type ExtractErrorType<P> = P extends PromiseWithError<any, infer E> ? E : unknown; // 自动推导错误类型的钩子 export function useTypedQuery< TQueryFn extends () => Promise<any>, TQueryFnData = PromiseResolve<ReturnType<TQueryFn>>, TError = ExtractErrorType<ReturnType<TQueryFn>>, TData = TQueryFnData, TQueryKey extends readonly any[] = any[] >( options: Omit<UseQueryOptions<TQueryFnData, TError, TData, TQueryKey>, 'queryFn'> & { queryFn: TQueryFn; } ): UseQueryResult<TData, TError> { return useQuery(options); } // 使用时完全自动推导,不用指定任何泛型(如果TypeScript能识别getData的类型) const { data, error } = useTypedQuery({ queryKey: ['resource', resourceId], queryFn: () => getData<MyResource>({ resourceId }), }); // error的类型自动是IProblemDetails | null
注意事项
- 确保你的
getData函数在错误时确实reject的是IProblemDetails类型,最好在ApiWithAuth的get方法里就做好类型解析,避免出现类型不匹配的情况。 - TanStack Query的
error属性类型是TError | null,所以使用时要先判断error是否存在,再访问它的属性(比如error?.title)。
备注:内容来源于stack exchange,提问作者Cătălin Andrei Preda
相关产品推荐
相关产品推荐

