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
相关产品推荐
相关产品推荐

