You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.16 10:29:33