React Query 的 useQuery 为什么在组件多次渲染时重复请求?
我在一个列表组件里用 useQuery 获取数据,明明传了相同的 queryKey,但每次父组件状态更新导致重渲染时,它都会重新发请求,不是应该缓存吗?
我试过加 staleTime,也确认了 queryKey 没变,但还是没用。是不是哪里配置错了?
const { data } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(res => res.json()),
staleTime: 1000 * 60 * 5, // 5分钟
});
先说最可能的原因:你确定 queryKey 真的没变吗?
我知道你传的是
['users']这个固定数组,但问题往往不在这里。让我猜一下你的场景——父组件状态更新,然后子组件(用了 useQuery 的这个)会重新渲染,对吧?关键点来了:如果你的子组件在父组件更新时是重新挂载而不是重新渲染,那缓存是会失效的。什么叫重新挂载?比如你用了条件渲染
{showList && },当 showList 从 false 变 true 又变 false 再变 true,组件会被卸载重建,这时候 React Query 会认为这是一个新的查询。还有一种情况是组件结构问题——如果你的列表组件在父组件 return 里的位置不稳定,比如用了某种动态渲染逻辑,也可能导致重新挂载。
解决方案:
第一种,检查组件是否被卸载重建。如果是,用
useMemo稳定 queryKey 只是辅助手段,真正要解决的是组件挂载问题。第二种,如果确实需要条件渲染,可以考虑把 useQuery 放到父组件或者更上层的组件里,让它不要跟着条件渲染一起挂载卸载。
第三种,检查你的 QueryClient 配置。确保 QueryClient 是放在最外层、只初始化一次的,别放在会因为父组件渲染而重新创建的位置。
第四种,如果以上都不是,可以试试显式控制:
不过这只是治标,先排查组件是否被卸载重建才是治本。
你方便说说你的组件结构吗?父组件状态更新的时候,那个列表组件是直接重新渲染,还是整个被拿掉又放回来?
refetchOnWindowFocus: true,而且每次组件重渲染时如果检测到“可能 stale”,就会触发 refetch。你加了
staleTime是对的,但注意:React Query 的默认staleTime是 0!你写 5 分钟是对的,但要确认有没有被其他地方覆盖了(比如全局配置没生效,或者 queryClient 初始化时没传)。不过更常见的坑是:父组件重渲染时,
useQuery的queryFn被重新创建了,哪怕queryKey没变,React Query 也会认为“函数变了,可能结果也变了”,于是触发请求。所以关键点是:
queryFn必须用useCallback或者直接提成外部函数,保证引用稳定。比如这样改:
或者用
useCallback包一下(虽然没必要,因为函数体没依赖):再检查下你有没有全局配置里把
staleTime覆盖成 0,或者有没有useQuery的其他参数(比如enabled、refetchOnMount)在捣乱。代码放这了,你试试看。要是还不行,八成是父组件传了个新对象当
queryKey(比如['users', someObject],每次someObject都是新引用),这种也容易踩。