如何在TanStack React Query中复用useQuery缓存实现聚合查询?
我正在使用TanStack React Query从API获取对象列表(记为Query_A)。现在我希望获取并缓存该数据集的聚合版本(记为Query_B)。有没有巧妙的内置方法可以在Query_B中复用Query_A已获取并缓存的数据?如果没有,这是否属于反模式,所以我从未见过此类实现?我是否应该解耦两个查询,重复执行相同查询以缓存两种不同结果?
我想知道是否可以/应该这样实现:
export const Query_A = ( parameters: string[] ) => { return useQuery({ queryKey: ['Query_A', parameters], queryFn: async () => { const rawResult = await apiClient.request('/my/endpoint/', parameters); return rawResult; } }) }; export const Query_B = ( parameters: string[] ) => { return useQuery({ queryKey: ['Query_B', parameters], queryFn: async () => { const { data, error, isLoading, isFetching, isError, isSuccess, } = Query_A(parameters); const myPromise = new Promise((resolve, reject) => { while ((isLoading || isFetching) && data === undefined) { setTimeout(() => { console.debug('waiting for data to be returned'); }, 100); } if (isSuccess) { resolve(data); } if (isError) { reject(new Error('Something went wrong!', error)); } }); await myPromise.then((data) => { return data; }).catch((error) => { return error; }); } }) };
1. 你写的实现方式不可行
首先明确:你的代码违反了React Hook的核心规则——不能在非组件顶层、非自定义Hook顶层的地方调用Hook。在Query_B的queryFn内部调用Query_A(本质是调用useQuery)会导致Hook调用顺序混乱,直接触发React报错,完全不可用。另外,手动用循环+定时器等待数据的写法既低效又不符合React的异步逻辑模型,完全没必要。
2. 复用缓存的正确姿势
TanStack React Query提供了两种优雅的方式来复用Query_A的缓存数据,不需要重复请求API:
方式一:用select参数做轻量聚合(推荐)
如果你的聚合逻辑只是对原始数据做简单转换,不需要单独缓存聚合结果,直接在Query_A的useQuery中通过select选项返回聚合后的数据即可:
export const useQueryA = (parameters: string[]) => { return useQuery({ queryKey: ['Query_A', parameters], queryFn: async () => { const rawResult = await apiClient.request('/my/endpoint/', parameters); return rawResult; }, }); }; // 在组件中使用时,直接通过select获取聚合结果 const { data: aggregatedData } = useQueryA(parameters, { select: (rawData) => { // 这里写你的聚合逻辑,比如求和、分组、过滤等 return rawData.reduce((total, item) => total + item.amount, 0); }, });
这种方式的优势是完全复用Query_A的缓存,不需要额外的查询实例,当Query_A的原始数据更新时,聚合结果会自动同步更新,逻辑简洁高效。
方式二:用useQueryClient读取缓存(适合单独缓存聚合结果)
如果你需要把聚合结果作为独立的缓存项(比如多个组件需要复用相同的聚合结果,或者聚合逻辑复杂需要单独维护),可以通过useQueryClient直接读取Query_A的缓存:
import { useQuery, useQueryClient } from '@tanstack/react-query'; export const useQueryA = (parameters: string[]) => { return useQuery({ queryKey: ['Query_A', parameters], queryFn: async () => { const rawResult = await apiClient.request('/my/endpoint/', parameters); return rawResult; }, }); }; export const useQueryB = (parameters: string[]) => { const queryClient = useQueryClient(); return useQuery({ queryKey: ['Query_B', parameters], queryFn: async () => { // 优先从缓存取Query_A的数据,没有则自动执行请求 const rawData = await queryClient.fetchQuery({ queryKey: ['Query_A', parameters], queryFn: async () => { const rawResult = await apiClient.request('/my/endpoint/', parameters); return rawResult; }, }); // 执行聚合逻辑 const aggregatedData = rawData.reduce((total, item) => total + item.amount, 0); return aggregatedData; }, // 可选:用Query_A的缓存作为初始值,提升加载速度 initialData: () => { const cachedRawData = queryClient.getQueryData(['Query_A', parameters]); if (cachedRawData) { return cachedRawData.reduce((total, item) => total + item.amount, 0); } return undefined; }, }); };
这里fetchQuery会先检查缓存,只有缓存不存在时才会发起API请求,确保不会重复请求。同时initialData可以让Query_B直接使用Query_A的已有缓存作为初始数据,避免不必要的等待。
3. 关于反模式的疑问
复用已有缓存数据完全不是反模式,反而完全符合React Query的设计初衷——减少冗余请求,高效利用缓存。你之前没见过类似实现,可能是因为很多场景下用select就足够解决问题,不需要单独的Query_B。
如果你的聚合逻辑复杂,或者需要在多个地方复用相同的聚合结果,那么单独创建Query_B并复用Query_A的缓存是完全合理的做法,不属于反模式。
内容的提问来源于stack exchange,提问作者Torc

