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

如何为react-query的useQuery设置多类型返回值?

解决方案

一、优化现有代码,支持联合类型返回

问题核心是TypeScript无法自动推断query.data的具体类型(它是Task[] | PaginationType | undefined的联合类型),导致自动补全失效。我们可以通过类型守卫和泛型约束让类型系统明确当前场景的返回类型。

1. 完善API函数的类型定义

修复getTasks的返回值类型,确保无参数时的返回值也明确:

export const getTasks = (params?: SearchTasksDTO): Promise<Task[] | PaginationType> => {
  if (params) { 
    return axios.get('/tasks', { params }).then(res => {
      return params.paginate ? res.data as PaginationType : res.data as Task[];
    });
  }
  // 无参数时默认返回Task[]
  return axios.get('/tasks').then(res => res.data as Task[]);
};

2. 添加类型守卫并改造自定义Hook

// 类型守卫:判断返回值是否为PaginationType
const isPaginationType = (data: Task[] | PaginationType): data is PaginationType => {
  return typeof (data as PaginationType).hasMore === 'boolean';
};

// 给Hook添加泛型,让调用方指定返回类型
type UseTasksOptions<T extends Task[] | PaginationType> = {
  params?: SearchTasksDTO;
  config?: QueryConfig<typeof getTasks>;
};

export const useTasks = <T extends Task[] | PaginationType>({ config = {}, params }: UseTasksOptions<T>) => {
  const queryKey: QueryKey = params ? ['tasks', params] : ['tasks'];

  const query = useQuery<T>({
    ...config,
    queryKey,
    queryFn: () => getTasks(params) as Promise<T>,
  });

  // 使用类型守卫确保hasMore存在时才执行逻辑
  useEffect(() => {
    if (!query.isPreviousData && query.data && isPaginationType(query.data) && query.data.hasMore) {
      query.refetch();
    }
  }, [query.data, query.isPreviousData, params]);
  
  return query;
};

3. 组件中明确调用类型

在组件里通过泛型指定具体返回类型,获得完整自动补全:

// 获取分页数据
const { data: paginatedTasks } = useTasks<PaginationType>({
  params: { paginate: true, page: 1 }
});
// paginatedTasks?.totalPages 会被正确识别

// 获取全部任务列表
const { data: allTasks } = useTasks<Task[]>({
  params: { paginate: false }
});
// allTasks 自动识别为Task[]类型

二、分离关注点:拆分为两个独立Hook

如果分页和非分页的业务逻辑差异较大,更推荐拆分为两个独立Hook,彻底避免联合类型的模糊性:

1. 拆分API函数

// 获取全部任务(非分页)
export const getAllTasks = (params?: Omit<SearchTasksDTO, 'paginate'>): Promise<Task[]> => {
  return axios.get('/tasks', { params }).then(res => res.data as Task[]);
};

// 获取分页任务
export const getPaginatedTasks = (params: SearchTasksDTO & { paginate: true }): Promise<PaginationType> => {
  return axios.get('/tasks', { params }).then(res => res.data as PaginationType);
};

2. 分别创建自定义Hook

// 获取全部任务的Hook
export const useAllTasks = ({ config = {}, params }: {
  params?: Omit<SearchTasksDTO, 'paginate'>;
  config?: QueryConfig<typeof getAllTasks>;
}) => {
  const queryKey: QueryKey = params ? ['all-tasks', params] : ['all-tasks'];
  
  return useQuery<Task[]>({
    ...config,
    queryKey,
    queryFn: () => getAllTasks(params),
  });
};

// 获取分页任务的Hook
export const usePaginatedTasks = ({ config = {}, params }: {
  params: SearchTasksDTO & { paginate: true };
  config?: QueryConfig<typeof getPaginatedTasks>;
}) => {
  const queryKey: QueryKey = ['paginated-tasks', params];
  
  const query = useQuery<PaginationType>({
    ...config,
    queryKey,
    queryFn: () => getPaginatedTasks(params),
  });

  useEffect(() => {
    if (!query.isPreviousData && query.data?.hasMore) {
      query.refetch();
    }
  }, [query.data, query.isPreviousData, params]);
  
  return query;
};

3. 组件中调用

// 使用分页Hook
const { data: paginatedTasks } = usePaginatedTasks({
  params: { paginate: true, page: 1, search: 'test' }
});
// paginatedTasks.totalPages、paginatedTasks.hasMore 等属性自动补全正常

// 使用全部任务Hook
const { data: allTasks } = useAllTasks({
  params: { search: 'test' }
});
// allTasks 自动识别为Task[]类型

关键说明

  • 类型守卫是处理联合类型的核心手段,能让TypeScript在特定分支下明确变量的具体类型
  • 拆分Hook的方式更符合单一职责原则,代码可读性和维护性更高,尤其适合业务逻辑复杂的场景
  • 无论哪种方案,都要确保API函数的返回类型明确,避免any类型导致的类型推断失效

内容的提问来源于stack exchange,提问作者Galih indra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 19:58:24