React Query 合并多个useQuery钩子返回值的类型安全问题
问题根源
之前的泛型定义没有对可变元组中每个UseQueryResult的独立data类型做逐位提取,TypeScript会将所有入参的data类型合并为联合类型,或兜底为unknown,无法生成顺序、类型一一对应的元组。
另外原实现存在两个逻辑问题:
isSuccess判断逻辑错误:任意一个查询成功就返回true,不符合合并查询的预期,正确逻辑应为所有查询都成功时整体才为成功状态reduce过滤undefined的逻辑会破坏数组位置和类型的对应关系:如果靠前的查询还在加载、data为undefined,后面查询的返回值会被提前放到数组前部,和元组类型的索引错位,极易引发业务bug
修正实现
使用TypeScript 4.0+支持的可变元组映射(Mapped Tuple Type),逐位提取每个查询的data类型,即可得到精准的元组类型推断:
import type { UseQueryResult, QueryObserverBaseResult } from '@tanstack/react-query'; export function isAnyProperty( property: keyof QueryObserverBaseResult<unknown, unknown>, ...queries: UseQueryResult<unknown, unknown>[] ) { // 增加Boolean包装避免falsy值误判 return queries.some((query) => Boolean(query[property])); } // 工具类型:逐位提取查询元组中每个项的data类型,生成对应顺序的data元组 type QueryDataTuple<T extends readonly UseQueryResult<any, any>[]> = { [K in keyof T]: T[K] extends UseQueryResult<infer TData, any> ? TData : never; }; export function combineQueryResults<TQueries extends readonly UseQueryResult<any, any>[]>( ...queries: TQueries ) { // 保留每个位置的data(含加载中的undefined),和元组索引严格对齐 const data = queries.map(query => query.data) as QueryDataTuple<TQueries>; return { data, isLoading: isAnyProperty('isLoading', ...queries), isFetching: isAnyProperty('isFetching', ...queries), // 修正isSuccess判断:所有查询成功才返回true isSuccess: queries.every(query => query.isSuccess), }; }
类型效果
传入多个不同返回类型的useQuery结果时,TS会自动推断出顺序匹配的元组类型:比如传入UseQueryResult<Vod.ConstructedAsset[]>、UseQueryResult<Group.Union[]>、UseQueryResult<unknown[]>三个查询,最终返回的data类型为[Vod.ConstructedAsset[] | undefined, Group.Union[] | undefined, unknown[] | undefined],完全匹配每个查询的返回值类型。
如果确实需要过滤掉undefined值,可在使用侧通过isSuccess判断整体加载完成后,再通过非空断言取数即可,不建议在合并函数中过滤破坏索引对应关系。
内容的提问来源于stack exchange,提问作者Bjorno
相关产品推荐
相关产品推荐

