news 2026/9/10 15:05:29

Vue Query 数据预取(Prefetching)完整指南:用 queryClient.query 与 infiniteQuery 预热缓存

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Vue Query 数据预取(Prefetching)完整指南:用 queryClient.query 与 infiniteQuery 预热缓存

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.queryqueryClient.infiniteQuery在数据被需要之前将其预热到缓存中,涵盖staleTimestaleTime: 'static'、垃圾回收、Infinite Query 多页预取等关键行为,并结合仓库源码剖析底层实现。读完你将掌握一套可落地、可复用的预取方案,显著提升页面切换与交互的响应速度。

什么是预取(Prefetching)

预取是指:如果你对用户接下来的行为有足够把握(例如用户进入列表页后大概率会点击第一条进入详情页),就可以提前把“将来才需要”的数据请求发出去,并写入查询缓存。这样当useQuery真正挂载时,数据已经在缓存里,组件可以立即渲染,而不必等待网络往返。

在 Vue Query 中完成预取的入口就是queryClient.queryqueryClient.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,核心决策只有两步:

  1. defaultQueryOptions补齐默认配置,并通过this.#queryCache.build(this, defaultedOptions)构建/复用 Query 实例;
  2. 调用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 的说明

从源码注释可以确认,早期版本使用的prefetchQueryfetchQueryensureQueryDataprefetchInfiniteQueryfetchInfiniteQuery在 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,仓库还提供了响应式的组合式预取钩子,适合放在路由守卫、父组件setupwatchEffect中随响应式依赖自动触发:

  • 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 同样适用。

实践建议

  1. 在路由进入前预取:配合 Vue Router 的beforeEnter守卫或路由组件的setup中调用usePrefetchQuery,让详情页/下一页数据先于渲染就绪。
  2. 合理设置staleTime:高频稳定的数据可设置较长staleTime(或直接用staleTime: 'static')避免重复请求;易变数据保持默认短过期策略。
  3. 务必吞掉错误:所有预取调用统一使用void ... .catch(noop)模式,避免出现 unhandled promise rejection,同时把重试与错误 UI 交给useQuery
  4. 控制 Infinite Query 的预取深度pages不是越大越好,按用户滚动行为预估 1~3 页即可,同时保证getNextPageParam正确实现终止条件。
  5. 关注内存:预取后若无人订阅,数据会在gcTime后被自动回收,无需手动清理;若想长期驻留缓存,保持相应useQuery实例存活或配合staleTime: 'static'使用。

预取是 Vue Query 提升体验性价比最高的手段之一——把“等待数据”变成“数据等待”,配合staleTimestatic与多页预取,即可构建丝滑的渐进式加载体验。相关核心源码与测试可继续深入阅读 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 15:03:50

告别混乱交互:Telegraf场景管理与Wizard系统的7个实战技巧

告别混乱交互&#xff1a;Telegraf场景管理与Wizard系统的7个实战技巧 你是否还在为Telegram机器人的多步骤交互头疼&#xff1f;用户输入混乱、对话逻辑跳转复杂、状态管理繁琐——这些问题让许多开发者望而却步。本文将系统讲解Telegraf框架中场景管理与Wizard系统的核心用法…

作者头像 李华
网站建设 2026/9/10 14:59:10

Apache Kafka Streams 数据类型与序列化(Serdes)完全指南

Apache Kafka Streams 数据类型与序列化&#xff08;Serdes&#xff09;完全指南 【免费下载链接】Kafka Apache Kafka - A distributed event streaming platform 项目地址: https://gitcode.com/GitHub_Trending/kafka4/kafka 导读 Kafka Streams 作为一个基于 Kafka…

作者头像 李华