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

React Query自定义useQuery钩子TypeScript类型推导异常问题

解决自定义useQuery钩子的TypeScript类型推导问题

问题根源

你当前实现里,typeof select extends undefined的条件判断无法正确工作——因为可选参数select的类型是((data: PaginatedExerciseInfo<Exercises>) => number) | undefined,TypeScript在泛型实例化阶段无法自动根据是否传入参数缩小这个联合类型,导致第三个泛型参数始终被解析为undefined,进而错误推导返回的data类型。

解决方案1:用显式泛型参数捕获select类型

将select的类型作为泛型参数并设置默认值为undefined,让TypeScript能根据是否传入select自动推导最终返回数据类型:

export const getExercises = async (muscleGroup: MuscleGroup, page: number, perPage: number) => {
  const { data } = await axios.get(`http://localhost:8000/exercises/${muscleGroup}?page=${page}&per_page=${perPage}&sort=-date`);
  return data;
};

export const useGetExercises = <
  Exercises extends string,
  SelectFn extends ((data: PaginatedExerciseInfo<Exercises>) => number) | undefined = undefined
>(
  muscleGroup: MuscleGroup,
  select?: SelectFn
) => {
  const { page, perPage } = useContext(PaginationContext);
  return useQuery<
    PaginatedExerciseInfo<Exercises>,
    unknown,
    SelectFn extends undefined ? PaginatedExerciseInfo<Exercises> : number
  >({
    queryKey: [muscleGroup, 'exercises', page, perPage],
    queryFn: () => getExercises(muscleGroup, page, perPage),
    select: select
  });
};

解决方案2:用函数重载(更直观)

通过函数重载分别定义两种调用场景的类型,让TypeScript直接匹配对应的返回类型:

import { UseQueryResult } from '@tanstack/react-query';

export const getExercises = async (muscleGroup: MuscleGroup, page: number, perPage: number) => {
  const { data } = await axios.get(`http://localhost:8000/exercises/${muscleGroup}?page=${page}&per_page=${perPage}&sort=-date`);
  return data;
};

// 重载1:未传入select时的类型定义
export function useGetExercises<Exercises extends string>(
  muscleGroup: MuscleGroup
): UseQueryResult<PaginatedExerciseInfo<Exercises> | undefined, unknown>;

// 重载2:传入select时的类型定义
export function useGetExercises<Exercises extends string>(
  muscleGroup: MuscleGroup,
  select: (data: PaginatedExerciseInfo<Exercises>) => number
): UseQueryResult<number | undefined, unknown>;

// 通用实现
export function useGetExercises<Exercises extends string>(
  muscleGroup: MuscleGroup,
  select?: (data: PaginatedExerciseInfo<Exercises>) => number
) {
  const { page, perPage } = useContext(PaginationContext);
  return useQuery({
    queryKey: [muscleGroup, 'exercises', page, perPage],
    queryFn: () => getExercises(muscleGroup, page, perPage),
    select: select
  });
}

效果验证

  • 未传入select时:const { data: exercises } = useGetExercises<Exercise>(muscleGroup),exercises的类型会正确推导为PaginatedExerciseInfo<Exercise> | undefined
  • 传入select时:const { data: count } = useGetExercises<Exercise>(muscleGroup, (data) => data.total),count的类型会正确推导为number | undefined

内容的提问来源于stack exchange,提问作者G.W.F

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 23:47:19