React Query 的 useQuery 为什么在组件多次渲染时重复请求?

智慧 阅读 34

我在一个列表组件里用 useQuery 获取数据,明明传了相同的 queryKey,但每次父组件状态更新导致重渲染时,它都会重新发请求,不是应该缓存吗?

我试过加 staleTime,也确认了 queryKey 没变,但还是没用。是不是哪里配置错了?

const { data } = useQuery({
  queryKey: ['users'],
  queryFn: () => fetch('/api/users').then(res => res.json()),
  staleTime: 1000 * 60 * 5, // 5分钟
});
我来解答 赞 29 收藏
二维码
手机扫码查看
2 条解答
Top丶梓晴
这个问题其实挺常见的,让我帮你拆解一下。

先说最可能的原因:你确定 queryKey 真的没变吗?

我知道你传的是 ['users'] 这个固定数组,但问题往往不在这里。让我猜一下你的场景——父组件状态更新,然后子组件(用了 useQuery 的这个)会重新渲染,对吧?

关键点来了:如果你的子组件在父组件更新时是重新挂载而不是重新渲染,那缓存是会失效的。什么叫重新挂载?比如你用了条件渲染 {showList && },当 showList 从 false 变 true 又变 false 再变 true,组件会被卸载重建,这时候 React Query 会认为这是一个新的查询。

还有一种情况是组件结构问题——如果你的列表组件在父组件 return 里的位置不稳定,比如用了某种动态渲染逻辑,也可能导致重新挂载。

解决方案:

第一种,检查组件是否被卸载重建。如果是,用 useMemo 稳定 queryKey 只是辅助手段,真正要解决的是组件挂载问题。

第二种,如果确实需要条件渲染,可以考虑把 useQuery 放到父组件或者更上层的组件里,让它不要跟着条件渲染一起挂载卸载。

第三种,检查你的 QueryClient 配置。确保 QueryClient 是放在最外层、只初始化一次的,别放在会因为父组件渲染而重新创建的位置。

第四种,如果以上都不是,可以试试显式控制:

const { data } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(res => res.json()),
staleTime: 1000 * 60 * 5,
refetchOnMount: false, // 挂载时不重新请求
refetchOnWindowFocus: false, // 窗口聚焦时不重新请求
});


不过这只是治标,先排查组件是否被卸载重建才是治本。

你方便说说你的组件结构吗?父组件状态更新的时候,那个列表组件是直接重新渲染,还是整个被拿掉又放回来?
点赞 1
2026-03-10 23:05
诗雅
诗雅 Lv1
你这个代码其实没写错,但问题出在 React Query 的默认行为上——它默认是 refetchOnWindowFocus: true,而且每次组件重渲染时如果检测到“可能 stale”,就会触发 refetch。

你加了 staleTime 是对的,但注意:React Query 的默认 staleTime 是 0!你写 5 分钟是对的,但要确认有没有被其他地方覆盖了(比如全局配置没生效,或者 queryClient 初始化时没传)。

不过更常见的坑是:父组件重渲染时,useQueryqueryFn 被重新创建了,哪怕 queryKey 没变,React Query 也会认为“函数变了,可能结果也变了”,于是触发请求。

所以关键点是:queryFn 必须用 useCallback 或者直接提成外部函数,保证引用稳定。

比如这样改:

const fetchUsers = () => fetch('/api/users').then(res => res.json());

function UserList() {
const { data } = useQuery({
queryKey: ['users'],
queryFn: fetchUsers,
staleTime: 1000 * 60 * 5,
});

// ...
}


或者用 useCallback 包一下(虽然没必要,因为函数体没依赖):

const fetchUsers = useCallback(() => fetch('/api/users').then(res => res.json()), []);


再检查下你有没有全局配置里把 staleTime 覆盖成 0,或者有没有 useQuery 的其他参数(比如 enabledrefetchOnMount)在捣乱。

代码放这了,你试试看。要是还不行,八成是父组件传了个新对象当 queryKey(比如 ['users', someObject],每次 someObject 都是新引用),这种也容易踩。
点赞 6
2026-02-27 01:00