Vue Query 数据预取(Prefetching)完整指南:用 queryClient.query 与 infiniteQuery 预热缓存
【免费下载链接】query🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query
导读:在 TanStack Vue Query 应用中,许多数据是用户操作之后才需要的(例如详情页、下一页列表)。本指南讲解如何利用queryClient.query与queryClient.infiniteQuery在数据被需要之前将其预热到缓存中,涵盖staleTime、staleTime: 'static'、垃圾回收、Infinite Query 多页预取等关键行为,并结合仓库源码剖析底层实现。读完你将掌握一套可落地、可复用的预取方案,显著提升页面切换与交互的响应速度。
什么是预取(Prefetching)
预取是指:如果你对用户接下来的行为有足够把握(例如用户进入列表页后大概率会点击第一条进入详情页),就可以提前把“将来才需要”的数据请求发出去,并写入查询缓存。这样当useQuery真正挂载时,数据已经在缓存里,组件可以立即渲染,而不必等待网络往返。
在 Vue Query 中完成预取的入口就是queryClient.query与queryClient.infiniteQuery(对应 Infinite Query 场景)。它们会把请求结果像普通查询一样写入缓存:
import { noop } from '@tanstack/vue-query' const prefetchTodos = async () => { // The results of this query will be cached like a normal query await queryClient .query({ queryKey: ['todos'], queryFn: fetchTodos, }) .catch(noop) }提示:在
<script setup>或组件的setup()中,queryClient通过const queryClient = useQueryClient()获取,其中useQueryClient由@tanstack/vue-query导出,示例参见 packages/vue-query/README.md。
预取的核心行为规则
理解预取,关键是理解下面几条行为约定,它们决定了缓存何时被命中、何时被重新拉取:
如果该查询在缓存中已有 fresh(新鲜)数据,则不会再次发起请求。预取尊重既有的新鲜缓存,不会做无谓的网络请求。
staleTime决定数据是否过期。例如:queryClient.query({ queryKey: ['todos'], queryFn: fetchTodos, staleTime: 5000, // 5 秒内视为新鲜,超过则重新拉取 })当缓存数据已超过指定的
staleTime时,预取会重新执行queryFn;未超过则直接复用缓存数据。错误处理交给
useQuery,预取只负责“点火”。因为useQuery会负责重试与错误展示,预取阶段你只需要忽略 Promise:用void忽略返回的 Promise,用.catch(noop)吞掉错误即可(noop是 Vue Query 导出的空操作函数,见 packages/vue-query/src/queryClient.ts 中废弃 API 的用法注释)。staleTime: 'static':只要有缓存数据就永远返回。当查询被标记为static时,预取不会因数据“过期”而重新请求,只要缓存存在就直接返回。缓存不会永久驻留:如果某个被预取的查询始终没有
useQuery实例订阅(例如用户最终没有进入对应页面),在达到gcTime(垃圾回收时间)之后它会被删除并回收。
为什么重试被关闭:源码里的retry: false
预取阶段为什么不重试?从 packages/query-core/src/queryClient.ts 的实现可以看到,query()在发起前会做一次兜底处理:
// https://github.com/tannerlinsley/react-query/issues/652 if (defaultedOptions.retry === undefined) { defaultedOptions.retry = false }也就是说,当调用方未显式指定retry时,一次性的queryClient.query调用默认不会重试。失败的重试与错误 UI 呈现是useQuery等观察者的职责,预取只负责把数据放进缓存,失败就静默忽略——这正是void+.catch(noop)组合存在的意义。
源码视角:query()是如何决定“取或缓存”的
query()的完整逻辑位于 packages/query-core/src/queryClient.ts,核心决策只有两步:
- 用
defaultQueryOptions补齐默认配置,并通过this.#queryCache.build(this, defaultedOptions)构建/复用 Query 实例; - 调用
query.isStaleByTime(...)判断数据是否过期:- 过期 →
await query.fetch(defaultedOptions)重新拉取; - 未过期 → 直接返回
query.state.data(缓存命中)。
- 过期 →
“是否过期”的判定在 packages/query-core/src/query.ts 中实现:
isStaleByTime(staleTime: StaleTime = 0): boolean { // no data is always stale if (this.state.data === undefined) { return true } // static is never stale if (staleTime === 'static') { return false } // if the query is invalidated, it is stale if (this.state.isInvalidated) { return true } return !timeUntilStale(this.state.dataUpdatedAt, staleTime) }从源码可以推断出三条结论:
- 没有任何数据时永远是 stale 的,所以首次预取必然触发请求;
staleTime === 'static'直接短路返回false,即“static 永不新鲜失效”,这正是staleTime: 'static'实现“只要缓存存在就直接返回”的底层原因(同时参考 packages/query-core/src/query.ts 的isStatic()方法);- 被
invalidate过的查询即使没超时也会被视为 stale,从而允许预取重新拉取。
而在 Vue 这一侧,packages/vue-query/src/queryClient.ts 对query()做了响应式封装:入参类型为MaybeRefDeep,内部通过cloneDeepUnref(options)深度解包 Vue 的ref/getter后再调用 query-core 的实现。因此你可以在预取参数里直接传入ref值,Vue Query 会自动解包。
关于废弃 API 的说明
从源码注释可以确认,早期版本使用的prefetchQuery、fetchQuery、ensureQueryData、prefetchInfiniteQuery、fetchInfiniteQuery在 packages/vue-query/src/queryClient.ts 与 packages/query-core/src/queryClient.ts 中均标记为@deprecated,官方建议统一改用queryClient.query({ ...options, staleTime: 'static' })或queryClient.infiniteQuery(options)。其中旧的prefetchQuery行为等价于fetchQuery().then(noop).catch(noop)——也就是“取数 + 吞错”,这与当前推荐的void query(...).catch(noop)写法语义一致。编写新代码时应直接使用新的query/infiniteQueryAPI。
组合式 API 变体:usePrefetchQuery 与 usePrefetchInfiniteQuery
除了命令式地调用queryClient,仓库还提供了响应式的组合式预取钩子,适合放在路由守卫、父组件setup或watchEffect中随响应式依赖自动触发:
usePrefetchQuery实现在 packages/vue-query/src/usePrefetchQuery.ts,核心是:watchEffect(() => { const resolvedOptions = isGetter(options) ? options() : unref(options) const clonedOptions = cloneDeepUnref(resolvedOptions) if (!client.getQueryState(clonedOptions.queryKey)) { void client.query(clonedOptions).catch(noop) } })它有两个值得注意的行为:只在查询状态不存在时发起预取(
getQueryState返回 undefined 才执行),并且支持传入ref/getter,queryKey 变化时会自动重新预取。usePrefetchInfiniteQuery实现在 packages/vue-query/src/usePrefetchInfiniteQuery.ts,结构完全对称,内部调用client.infiniteQuery(...)。
这些行为都有对应测试用例佐证,见 packages/vue-query/src/tests/usePrefetchQuery.test.ts:例如“查询状态已存在时不重复预取”“queryKey 响应式变化时重新预取”“自动解包 ref 参数”等。
预取 Infinite Queries(无限滚动查询)
Infinite Query 与普通查询一样可以预取,并且拥有独立的pages控制能力:
import { noop } from '@tanstack/vue-query' const prefetchProjects = async () => { // The results of this query will be cached like a normal query await queryClient .infiniteQuery({ queryKey: ['projects'], queryFn: fetchProjects, initialPageParam: 0, getNextPageParam: (lastPage, pages) => lastPage.nextCursor, pages: 3, // prefetch the first 3 pages }) .catch(noop) }关键行为如下:
- 默认只预取第一页。如果不传
pages,只有首页数据被拉取,并以给定queryKey存入缓存。 pages指定要预取的页数。上面的例子会按顺序预取前 3 页。- 每预取一页都会执行一次
getNextPageParam,用来推导“下一页的页码/游标”,从而构造下一次请求参数。 getNextPageParam返回undefined时预取立即停止。这是天然的终止条件——当接口不再返回下一页游标时,预取流程结束,不会出现无限请求。
从实现上看,infiniteQuery在 packages/query-core/src/queryClient.ts 中会先给选项打上options._type = 'infinite'标记,再复用query()的统一执行管线;因此“新鲜则命中、过期则拉取、static永不失效、无订阅则按gcTime回收”等所有规则对 Infinite Query 同样适用。
实践建议
- 在路由进入前预取:配合 Vue Router 的
beforeEnter守卫或路由组件的setup中调用usePrefetchQuery,让详情页/下一页数据先于渲染就绪。 - 合理设置
staleTime:高频稳定的数据可设置较长staleTime(或直接用staleTime: 'static')避免重复请求;易变数据保持默认短过期策略。 - 务必吞掉错误:所有预取调用统一使用
void ... .catch(noop)模式,避免出现 unhandled promise rejection,同时把重试与错误 UI 交给useQuery。 - 控制 Infinite Query 的预取深度:
pages不是越大越好,按用户滚动行为预估 1~3 页即可,同时保证getNextPageParam正确实现终止条件。 - 关注内存:预取后若无人订阅,数据会在
gcTime后被自动回收,无需手动清理;若想长期驻留缓存,保持相应useQuery实例存活或配合staleTime: 'static'使用。
预取是 Vue Query 提升体验性价比最高的手段之一——把“等待数据”变成“数据等待”,配合staleTime、static与多页预取,即可构建丝滑的渐进式加载体验。相关核心源码与测试可继续深入阅读 packages/vue-query/src/queryClient.ts、packages/query-core/src/queryClient.ts 与 packages/query-core/src/query.ts。
【免费下载链接】query🤖 Powerful asynchronous state management, server-state utilities and data fetching for the web. TS/JS, React Query, Solid Query, Svelte Query and Vue Query.项目地址: https://gitcode.com/GitHub_Trending/qu/query
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考