为何cacheTime设为0时React Query仍从缓存获取数据?
我正在将无缓存的Saga旧代码库迁移至React Query,初始阶段希望复刻现有非优化逻辑,之后逐步启用缓存,当前需求如下:
- 不希望在后台加载过期数据
- 默认禁用缓存
- 每次useQuery挂载都重新获取数据,正常加载
- 可为单个查询启用缓存
我的Query Client配置:
client = new QueryClient({ defaultOptions: { queries: { retry: false, staleTime: Infinity, cacheTime: 0 } }, });
为迁移封装的useRequest钩子:
export const useRequest = ( url, opts, ) => { return result = useQuery({ queryKey: [url], cacheTime: 0, ...opts, queryFn: () => request(url, 'GET'), }); };
我编写单元测试验证缓存是否禁用:
const { result: hook1, waitFor } = renderHook(() => useRequest('/jobs'), { wrapper, }); await waitFor(() => expect(hook1.current.isSuccess).toBe(true)); // Hook 2 load const { result: hook2 } = renderHook(() => useRequest('/jobs'), { wrapper, }); await waitFor(() => expect(hook2.current.isFetching).toBe(false)); // Assert cache await waitFor(() => expect(handler).toBeCalledTimes(2));
其中handler是API测试工具的间谍函数,但测试失败,调试发现仍从缓存加载数据。我原以为cacheTime: 0会立即失效缓存,为何此时仍从缓存取数?
移除staleTime: Infinity可修复该问题,但会破坏最后一项需求。第二个测试验证启用缓存时API不被调用两次:
// Hook 1 load const { result: hook1, waitFor } = renderHook(() => useRequest('/jobs', { cacheTime: 1000 }), { wrapper, }); await waitFor(() => expect(hook1.current.isSuccess).toBe(true)); // Hook 2 load const { result: hook2 } = renderHook(() => useRequest('/jobs'), { wrapper, }); await waitFor(() => expect(hook2.current.isFetching).toBe(false)); // Assert cache await waitFor(() => { expect(handler).toBeCalledTimes(1); });
若移除staleTime,数据会过期并在后台重新获取,导致测试失败。我无法理解当前行为,请解释原因。
核心问题:搞混了staleTime和cacheTime的作用
React Query里这两个参数的职责完全不同,别搞混:
staleTime:管数据从「新鲜」变「过期」的时间。数据新鲜的时候,组件挂载不会触发新请求,直接用缓存。你设了staleTime: Infinity,等于数据永远新鲜——不管cacheTime是多少,只要数据还在缓存里,就直接复用,根本不会发请求。cacheTime:管数据在缓存里待多久,没组件用它的时候,超过时间就会被清掉。但只要有组件在使用,或者数据还新鲜,cacheTime不影响要不要发请求。
你的第一个测试失败,就是因为staleTime: Infinity把数据锁成永久新鲜了,第二个钩子挂载直接用缓存,没发请求,所以handler只被调用1次。
同时满足所有需求的调整方案
要实现你的四个需求,得改默认配置和useRequest的逻辑:
默认禁用缓存,每次挂载都发请求:
默认配置不能设staleTime: Infinity,要设staleTime: 0(数据一存就过期),再加cacheTime: 0(组件卸载后立刻清缓存)。这样默认情况下,组件每次挂载都会触发新请求。单个查询启用缓存:
允许通过opts覆盖staleTime和cacheTime,比如给需要缓存的查询设staleTime: Infinity+足够长的cacheTime,让数据一直保持新鲜,复用缓存。
调整后的Query Client配置
client = new QueryClient({ defaultOptions: { queries: { retry: false, staleTime: 0, // 默认数据立即过期,触发请求 cacheTime: 0 // 默认组件卸载后立刻清缓存 } }, });
调整后的useRequest钩子
export const useRequest = (url, opts = {}) => { return useQuery({ queryKey: [url], // 默认禁用缓存的配置,允许被opts覆盖 staleTime: 0, cacheTime: 0, ...opts, queryFn: () => request(url, 'GET'), }); };
验证缓存禁用的测试(现在会通过)
const { result: hook1, waitFor } = renderHook(() => useRequest('/jobs'), { wrapper, }); await waitFor(() => expect(hook1.current.isSuccess).toBe(true)); const { result: hook2 } = renderHook(() => useRequest('/jobs'), { wrapper, }); await waitFor(() => expect(hook2.current.isFetching).toBe(false)); expect(handler).toBeCalledTimes(2); // 现在会成功
验证单个查询启用缓存的测试(现在会通过)
const { result: hook1, waitFor } = renderHook(() => useRequest('/jobs', { staleTime: Infinity, // 数据永远新鲜,复用缓存 cacheTime: 1000 }), { wrapper, }); await waitFor(() => expect(hook1.current.isSuccess).toBe(true)); const { result: hook2 } = renderHook(() => useRequest('/jobs'), { wrapper, }); await waitFor(() => expect(hook2.current.isFetching).toBe(false)); expect(handler).toBeCalledTimes(1); // 现在会成功
额外说明
- 给单个查询设
staleTime: Infinity时,数据会一直保持新鲜,后续挂载的组件直接用缓存,不会发请求,满足「启用缓存」的需求。 - 要避免后台加载过期数据,只要保持默认
staleTime:0,且不配置refetchInterval这类自动刷新项就行——React Query只有在数据新鲜时才不发请求,过期时会在组件挂载时同步发请求,不会偷偷在后台加载。
内容的提问来源于stack exchange,提问作者plusheen

