尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Vue Query 的 usePrefetchQuery:在渲染期间为 Suspense 提前预热查询缓存

Vue Query 的 usePrefetchQuery:在渲染期间为 Suspense 提前预热查询缓存 Vue Query 的 usePrefetchQuery在渲染期间为 Suspense 提前预热查询缓存【免费下载链接】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/queryusePrefetchQuery是 Vue Querytanstack/vue-query提供的声明式预取组合式函数composable它不返回任何数据只在组件渲染阶段把查询数据提前写入缓存供下游包裹在Suspense中的useQuery直接消费。本文以仓库中 usePrefetchQuery 参考文档 为主线结合packages/vue-query/src/usePrefetchQuery.ts源码与测试用例完整讲解其参数约束、响应式行为、与queryClient.query的关系以及如何用它落地Render-as-you-fetch数据获取模型。为什么需要 usePrefetchQueryVue Query 默认的 Suspense 模式是一个开箱即用的Fetch-on-render方案组件尝试挂载时才开始请求数据并挂起。而Render-as-you-fetch要求在组件挂载、甚至父组件被导入之前就提前把查询数据放进缓存从而缩短加载等待时间。参考 Suspense 指南 的说明官方推荐在路由回调或用户交互事件中执行 Prefetching 来提前加载数据。usePrefetchQuery正是为此而生的渲染期预取工具它放在某个useQuery组件的 Suspense 边界之前执行在渲染阶段同步发起预取让下游的挂起组件在恢复时直接命中缓存。它与命令式queryClient.query的核心区别在于它是响应式的、声明式的——查询键变化时自动重新预取无需手动在生命周期钩子里管理。函数签名与 OptionsusePrefetchQuery(options)options的类型定义在源码中为见 usePrefetchQuery.tsUsePrefetchQueryOptionsTQueryFnData, TError, TData, TQueryData, TQueryKey OmitKeyofQueryExecuteOptions..., queryFn { queryFn?: ExcludeQueryExecuteOptions...[queryFn], SkipToken }也就是说凡是可以传给queryClient.query的选项都可以原样传给usePrefetchQuery——因为它的实现本质上就是把 options 转发给queryClient.query。这些选项包括retry、retryDelay、networkMode、gcTime、queryFn、queryHash、queryKey、queryKeyHashFn、initialData、initialDataUpdatedAt、staleTime、structuralSharing、meta等参见 query-core 中 QueryOptions 的定义。需要注意部分useQuery才有的观察者专属选项在这里是被类型系统禁止的。类型测试 usePrefetchQuery.test-d.ts 明确验证了以下写法会触发ts-expect-errorusePrefetchQuery({ queryKey: queryKey(), queryFn: () Promise.resolve(5), // ts-expect-error TS2353 refetchInterval: 1000, }) usePrefetchQuery({ queryKey: queryKey(), queryFn: () Promise.resolve(5), // ts-expect-error TS2353 enabled: true, }) usePrefetchQuery({ queryKey: queryKey(), queryFn: () Promise.resolve(5), // ts-expect-error TS2353 throwOnError: true, })原因很直观预取只是填缓存的一次性动作没有组件在持续观察它refetchInterval定时刷新、enabled条件启用、throwOnError把错误抛给观察者这些依赖观察者的选项没有意义。queryKey必填类型QueryKey必填用于标识渲染期间要预取的查询是查询缓存的唯一寻址键。queryFn有条件的必填类型(context: QueryFunctionContextTQueryKey) TQueryFnData | PromiseTQueryFnData必填但仅当没有定义默认查询函数时。如果应用已经通过VueQueryPlugin的defaultOptions配置了全局默认queryFn则可以省略详见 Default Query Function 指南const vueQueryPluginOptions: VueQueryPluginOptions { queryClientConfig: { defaultOptions: { queries: { queryFn: defaultQueryFn } }, }, } app.use(VueQueryPlugin, vueQueryPluginOptions) // 配置了默认 queryFn 后只需传 key usePrefetchQuery({ queryKey: [/posts/${postId}], })另外源码将queryFn的类型显式排除了SkipToken见 usePrefetchQuery.ts类型测试也验证了把skipToken传给queryFn会被拒绝——因为预取必须真正发起请求不允许跳过。返回值什么都不返回usePrefetchQuery不返回任何内容返回类型为void见类型测试 usePrefetchQuery.test-d.ts。它的唯一用途是在渲染期间触发一次预取把数据写进缓存。典型使用场景是把它放在一个包裹着使用useQuery的suspense()的 Suspense 边界之前让下游组件挂起时数据已经就绪。参考 Suspense 指南 的完整搭配写法!-- 在 Suspense 边界之前预取 -- script setup import { usePrefetchQuery } from tanstack/vue-query usePrefetchQuery({ queryKey: [todos], queryFn: todoFetcher, }) /script template Suspense template #default SuspendableComponent / /template template #fallback divLoading.../div /template /Suspense /template被挂起的组件内部仍然是标准的useQuerysuspense()写法script import { defineComponent } from vue import { useQuery } from tanstack/vue-query const todoFetcher async () await fetch(https://jsonplaceholder.cypress.io/todos).then((response) response.json(), ) export default defineComponent({ name: SuspendableComponent, async setup() { const { data, suspense } useQuery({ queryKey: [todos], queryFn: todoFetcher, }) await suspense() return { data } }, }) /script源码实现一次响应式的缓存预热usePrefetchQuery的实现非常精简usePrefetchQuery.ts核心逻辑可以拆成四步export function usePrefetchQuery(options, queryClient?) { // 1. 开发模式下校验调用上下文 if (process.env.NODE_ENV development) { if (!getCurrentScope()) { console.warn( vue-query composable like useQuery() should only be used inside a setup() function or a running effect scope. They might otherwise lead to memory leaks., ) } } // 2. 获取 QueryClient可显式传入否则从上下文中解析 const client queryClient || useQueryClient() // 3. 用 watchEffect 包裹使预取成为响应式副作用 watchEffect(() { // 4. 解析 getter / ref深度解包后按需触发 queryClient.query const resolvedOptions isGetter(options) ? options() : unref(options) const clonedOptions cloneDeepUnref(resolvedOptions) if (!client.getQueryState(clonedOptions.queryKey)) { void client.query(clonedOptions).catch(noop) } }) }1. 必须在 setup 或 effect scope 中使用开发模式下如果getCurrentScope()为空即在 setup 函数或运行中的 effect scope 之外调用会输出内存泄漏警告。测试 usePrefetchQuery.test.ts 对该警告文案做了精确断言。2. QueryClient 的解析第二个参数queryClient可省略此时通过useQueryClient()从 Vue 的 provide/inject 上下文中解析。显式传入例如在测试中传入新建的QueryClient可以脱离插件上下文独立使用。3. watchEffect让预取跟随响应式状态这是usePrefetchQuery与命令式queryClient.query最本质的区别。所有解析与触发逻辑都包在watchEffect里因此options 可以是响应式对象MaybeRefDeep内部 ref 会被深度解包options 可以是一个getter 函数() options查询键变化时副作用自动重新执行为新的 key 发起预取。4. 缓存去重与错误吞没触发前会先检查client.getQueryState(queryKey)如果该查询的缓存状态已存在则不会重复发起请求。这样既避免了重复预取也保证了与queryClient.query的fresh 数据不重复获取语义一致详见 Prefetching 指南。请求通过void client.query(...).catch(noop)触发query返回 Promise 交给void忽略错误由.catch(noop)吞掉——因为预取失败不应导致渲染崩溃错误应由后续真正挂载的useQuery观察者来负责重试与展示。响应式行为的实证源码测试怎么说单元测试 usePrefetchQuery.test.ts 用vi.spyOn(queryClient, query)精确验证了上述行为缓存不存在时触发预取第 16-35 行const queryClient new QueryClient() const querySpy vi.spyOn(queryClient, query) const queryFn () Promise.resolve(prefetched) const key queryKey() usePrefetchQuery({ queryKey: key, queryFn }, queryClient) expect(querySpy).toHaveBeenCalledTimes(1) expect(querySpy).toHaveBeenCalledWith({ queryKey: key, queryFn })缓存已存在时不再触发第 37-53 行queryClient.setQueryData(key, existing) usePrefetchQuery({ queryKey: key, queryFn }, queryClient) expect(querySpy).not.toHaveBeenCalled()options 中的 ref 会被深度解包第 55-74 行const nestedRef ref(value) usePrefetchQuery( { queryKey: [...key, nestedRef], queryFn }, queryClient, ) // querySpy 收到的 queryKey 是 [...key, value]而不是 ref 本身查询键响应式变化时重新预取第 76-105 行const keyRef ref(first) usePrefetchQuery( () ({ queryKey: [...key, keyRef.value], queryFn }), queryClient, ) keyRef.value second await nextTick() expect(querySpy).toHaveBeenCalledTimes(2) // 第一次 first第二次 second这些测试共同印证了usePrefetchQuery的响应式不是空话——只要查询键依赖的任何响应式状态变化watchEffect就会自动为新的 key 补一次预取且每次都做缓存去重。底层链路usePrefetchQuery → queryClient.queryusePrefetchQuery内部最终调用的是 Vue Query 包装过的queryClient.query见 vue-query/src/queryClient.ts它会对选项执行cloneDeepUnref后转发给 query-core 的QueryClient.query。query-core 中的执行链路query-core/src/queryClient.ts值得展开async queryTQueryFnData, ...(options: QueryExecuteOptions...): PromiseTData { const defaultedOptions this.defaultQueryOptions(options) // 预取场景下 retry 默认关闭避免后台静默重试 if (defaultedOptions.retry undefined) { defaultedOptions.retry false } const query this.#queryCache.build(this, defaultedOptions) const isStale query.isStaleByTime( resolveQueryValue(defaultedOptions.staleTime, query), ) const queryData isStale ? await query.fetch(defaultedOptions) : (query.state.data as TQueryData) // 支持 select 对返回数据做投影 const select defaultedOptions.select if (select) { return select(queryData) } return queryData as unknown as TData }几个对预取行为有直接影响的要点retry默认被置为false预取是后台的尽力而为操作失败不值得反复重试占用资源错误统一交给下游观察者。staleTime决定是否真正发请求isStaleByTime检查数据是否过期fresh 数据直接走缓存分支query.state.data不产生网络请求过期数据才调用query.fetch。因此传入staleTime可以精细控制缓存多久后需要重新预取staleTime: static则始终返回缓存数据见 Prefetching 指南。没有useQuery观察的预取数据会被垃圾回收预取写入缓存的查询如果在gcTime指定的时间内没有任何useQuery实例消费会被 QueryCache 回收删除。使用注意与最佳实践小结位置放在使用useQuery的suspense()的组件所包裹的Suspense边界之前属于渲染期预热若希望更早可在路由回调/事件中用命令式 PrefetchingqueryClient.query/queryClient.infiniteQuery。类型约束不要传入refetchInterval、enabled、throwOnError类型层面直接报错queryFn不接受skipToken。响应式options 支持 ref 深度解包与 getter 形式查询键变化会自动补预取并自动去重缓存已存在时不重复请求。错误处理预取失败会被静默吞掉.catch(noop)不要依赖它的返回值恒为void失败后的展示与重试逻辑由下游useQuery负责。默认查询函数应用级配置了defaultOptions.queries.queryFn时queryFn可省略参考 Default Query Function。生命周期确保在setup()或 effect scope 内调用否则开发模式会输出内存泄漏警告。缓存一致性预取数据与普通查询共用同一份缓存遵循相同的staleTime/gcTime语义避免对同一queryKey在多个位置重复预取。结合本文的源码与测试证据usePrefetchQuery的本质可以一句话概括用 Vue 的响应式副作用包装一次性的缓存预热在渲染阶段把查询数据提前喂给 Suspense 下游的观察者从而让数据获取模型从 Fetch-on-render 平滑升级为 Render-as-you-fetch。【免费下载链接】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),仅供参考
返回列表