
Vue Query 中的 queryOptions构建可复用、类型安全的查询配置【免费下载链接】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 Query Vue 适配层Vue Query的核心工具函数queryOptions展开讲解它如何把queryKey、queryFn以及全部查询选项封装为一份可跨 Hook 与命令式 API 共享的类型安全配置。读完本文你将掌握queryOptions的签名与必填参数、Vue 响应式特性ref/computed/ getter的用法、useQuery/useQueries/queryClient.query等场景下的复用方式以及它通过 DataTag 机制为getQueryData、setQueryData等命令式 API 提供的端到端类型推导原理。什么是queryOptions在 Vue Query 中queryOptions是一个用于预定义查询选项的工具函数。它接收一个包含queryKey与其他查询选项的对象返回同一份配置这份配置既可以被useQuery等组合式 API 消费也可以被queryClient.query、prefetchQuery、invalidateQueries、getQueryData等命令式 API 消费。其官方签名为见 docs/framework/vue/reference/queryOptions.mdqueryOptions({ queryKey, ...options, })核心参数queryKey: QueryKey必填为这份查询选项生成的查询键。除了queryKey你可以把传给useQuery的几乎所有选项都传给queryOptions见 useQuery 选项参考例如queryFn、enabled、staleTime、gcTime、retry、select、placeholderData、initialData、refetchInterval、networkMode、structuralSharing等。换句话说queryOptions并不会限制你使用哪些选项它只是把「一份完整查询配置」提前到独立位置定义好供各处复用。为什么需要queryOptions把queryKey和queryFn拆散写在各个组件里会带来两个典型问题同样的 key 被多个queryFn意外复用造成数据互相覆盖或共享错乱配置无法在 Hook 与命令式 API 之间共享同一查询不得不在多处重复书写。queryOptions将queryKey与queryFn放在同一处「共址co-locate」定义让查询变得更安全、更易复用。仓库自带的 ESLint 插件也专门提供了prefer-query-options规则来强制这一最佳实践见 docs/eslint/prefer-query-options.md 与规则实现 prefer-query-options.rule.ts。例如规则认为下面这种写法不推荐/* eslint tanstack/query/prefer-query-options: error */ function Component({ id }) { const query useQuery({ queryKey: [get, id], queryFn: () Api.get(/foo/${id}), }) // ... }而推荐使用queryOptions先定义配置再交给useQuery/* eslint tanstack/query/prefer-query-options: error */ function getFooOptions(id) { return queryOptions({ queryKey: [get, id], queryFn: () Api.get(/foo/${id}), }) } function Component({ id }) { const query useQuery(getFooOptions(id)) // ... }基本用法将配置传入useQuery最直接的用法是先用queryOptions定义好配置通常在模块顶层或工厂函数中再传给useQueryimport { queryOptions, useQuery } from tanstack/vue-query const todosOptions queryOptions({ queryKey: [todos], queryFn: fetchTodos, staleTime: 30_000, }) // 组件内 const { data, isPending, error } useQuery(todosOptions)当查询依赖外部参数时可以把它封装成工厂函数const postOptions (id: number) queryOptions({ queryKey: [post, id], queryFn: () fetchPost(id), }) const { data } useQuery(postOptions(route.params.id))由于返回的是同一份配置对象你可以继续对选项做细粒度的覆盖或扩展const { data } useQuery({ ...postOptions(1), staleTime: 5_000, })Vue 特有的响应式选项支持与纯 TypeScript 框架如 React Query不同Vue Query 的queryOptions在类型层面针对 Vue 的响应式系统做了专门适配。从 queryOptions.ts 的类型定义可以看到QueryOptions中的queryKey与enabled两个字段被放宽为MaybeRefOrGetterqueryKey可以是普通数组、ref、computed或 getter 函数enabled可以是布尔值、ref、computed或 getter 函数。这意味着你可以写出这样的响应式配置import { computed, ref } from vue const id ref(1) const options queryOptions({ // queryKey 直接使用 ref queryKey: id, queryFn: () fetchPost(id.value), }) const enabled computed(() id.value ! null) const guardedOptions queryOptions({ queryKey: () [post, id.value] as const, queryFn: () fetchPost(id.value!), enabled, })仓库的类型测试queryOptions.test-d.ts明确覆盖了enabled支持computed/ref/ 布尔值 / getter 函数四种形态以及queryKey支持computed/ref/ getter 函数三种形态的编译期校验。这些测试还记录了历史回归issue #10452 曾破坏computedref 作为enabled与queryKey的类型说明响应式选项支持是 Vue Query 的重点保障特性。getter 函数形式Vue Query 的queryOptions还额外支持把整个选项对象包装成一个 getter 函数。从 queryOptions.ts 的重载签名可以看出除了传对象还可以传() DefinedInitialQueryOptions...或() UndefinedInitialQueryOptions...形式的函数返回类型也会相应保留函数形态const id ref(1) const options queryOptions(() ({ queryKey: [post, id.value], queryFn: () fetchPost(id.value), })) // options 本身是可调用函数使用时调用它 const { data } useQuery(options())getter 形式特别适合在setup内部引用响应式状态同时仍然保留完整的类型推导类型测试 queryOptions.test-d.ts 验证了 getter 配置可直接传给queryClient.invalidateQueries与queryClient.fetchQuery。跨 Hook 与命令式 API 共享queryOptions的价值在于「一份配置处处可用」。以下 API 都能直接消费queryOptions的返回值useQueriesuseQueries的queries数组可以接收由queryOptions生成的配置见 useQueries 参考文档const ids [1, 2, 3] const results useQueries({ queries: ids.map((id) queryOptions({ queryKey: [post, id], queryFn: () fetchPost(id), }), ), }) results.value // 只读 ref按输入顺序返回结果数组queryClient.query等命令式 APIqueryOptions返回的配置可以直接传给QueryClient的命令式方法import { QueryClient } from tanstack/vue-query const client new QueryClient() // 直接查询 const data await client.query(todosOptions) // 预取 await client.prefetchQuery(todosOptions) // 失效 await client.invalidateQueries(todosOptions)getQueryData/setQueryData因为queryOptions的返回类型中queryKey带有 DataTag 标记getQueryData/setQueryData能自动推导出该查询的数据类型详见下一节const todosOptions queryOptions({ queryKey: [todos], queryFn: fetchTodos, // 假设返回 Todo[] }) const client new QueryClient() // data 的类型被推导为 Todo[] | undefined const data client.getQueryData(todosOptions) // updater 的 prev 参数被推导为 Todo[] | undefined值必须为 Todo[] client.setQueryData(todosOptions, (prev) [...(prev ?? []), newTodo])类型安全的核心DataTag 机制queryOptions之所以能在useQuery、queryClient.query、getQueryData、setQueryData之间保持端到端类型推导关键在于它的返回类型会给queryKey打上一个「数据标签DataTag」。从 queryOptions.ts 可以看到所有重载的返回类型都形如type UndefinedInitialQueryOptionsWithDataTag... UndefinedInitialQueryOptions... QueryKeyWithDataTagTQueryKey, TQueryFnData, TErrorQueryKeyWithDataTag由tanstack/query-core提供它在queryKey上附加了queryFn返回的数据类型与错误类型信息。类型测试 queryOptions.test-d.ts 验证了这一行为有queryFn时queryKey[dataTagSymbol]的类型等于queryFn的返回类型Promise.resolve(5)→numberqueryFn同步返回时同样生效() 5→number没有queryFn时标签类型回退为unknown使用select时标签仍然记录queryFn的原始数据类型number保证getQueryData拿到的仍是未经过select的原始数据。正是借助这一标记client.query(options)的返回值可以被精确推导为number见 queryOptions.test-d.tssetQueryData(tagged, 5)则会在编译期报错queryOptions.test-d.ts从而把「key 与数据类型必须一致」这一约束落实到类型系统层面。运行时零开销queryOptions在运行时几乎不做事。查看 queryOptions.ts 的实现export function queryOptions(options: unknown) { return options }它只是把传入的对象原样返回所有类型能力都来自重载签名运行时不产生任何拷贝、哈希或包装。单元测试 queryOptions.test.ts 也直接断言了这一点expect(options).toBe(object)—— 返回值与入参是同一对象引用绝无修改。因此queryOptions可以放心放在模块顶层或高频调用路径上不会带来任何运行时性能成本也天然具备引用稳定性。initialData带来的类型收窄queryOptions的类型定义区分了「是否提供initialData」以及「initialData是否可能为undefined」从而对data的类型做精确收窄见 queryOptions.ts提供总是返回非空值的initialData对象或函数时data类型不含undefined不提供initialData时data类型包含undefinedinitialData可能返回undefined时data类型同样包含undefined在isSuccess为真后data会被收窄为非空类型。类型测试 queryOptions.test-d.ts 对以上四种情形逐一验证。这在实战中意味着如果你在配置里声明了必然存在的initialDatadata上就不再需要可选链或空值判断。与useQueries的 TypeScript 配合useQueries有一个已知的 TypeScript 限制由于它会一次性推断整个queries数组的类型内联书写inline的select参数无法从同对象的queryFn上下文推断select的data参数会回退为unknown详见 useQueries 参考文档useQueries({ queries: [ { queryKey: [post, 1], queryFn: () fetchPost(1), // ❌ data 在这里是 unknown select: (data) data.title, }, ], })官方推荐的正解之一就是使用queryOptions——因为它在到达useQueries之前就在单个对象内完成了类型解析const postOptions (id: number) queryOptions({ queryKey: [post, id], queryFn: () fetchPost(id), // ✅ data 被推导为 Post select: (data) data.title, }) useQueries({ queries: [postOptions(1), postOptions(2)] })即便你想对queryOptions的结果做内联覆盖spread 后再改select只要把整个 spread 再包进一次queryOptions类型同样能恢复见 useQueries.md。从源码看useQueries如何消费配置在底层useQueries会逐个解析数组里的每一项配置并交给QueriesObserver。查看 useQueries.ts 的实现关键流程是对queries数组逐项调用cloneDeepUnref解包响应式引用若enabled是函数先执行得到布尔值调用client.defaultQueryOptions(clonedOptions)将用户配置与QueryClient的默认选项合并设置_optimisticResults后交给QueriesObserver。由此可见queryOptions产出的普通对象与手写对象在此处没有任何运行时差异——两者都会经过相同的defaultQueryOptions归一化流程这也再次印证了queryOptions的全部价值集中在类型安全与代码组织层面。在 Vue 项目中落地queryOptions的最佳实践综合官方文档、源码与测试推荐在 Vue Query 项目中这样使用queryOptions为每个数据域建立选项工厂把queryKey、queryFn与默认选项staleTime、gcTime、retry等封装在queryOptions工厂函数中组件只负责消费不重复书写 key优先复用而非覆盖在useQuery/useQueries/prefetchQuery/invalidateQueries/getQueryData/setQueryData之间共享同一份配置保证 key 与类型始终一致利用响应式重载queryKey、enabled直接传ref/computed/ getter让查询自动响应状态变化无需手动同步利用类型收窄能声明非空initialData时尽量声明减少组件内的空值判断搭配 ESLint 规则启用tanstack/query/prefer-query-options规则规则定义见 prefer-query-options.rule.ts从工程层面约束团队统一使用queryOptions避免 key 与queryFn分离带来的隐患。总结queryOptions是 Vue Query 中连接「声明配置」与「消费查询」的枢纽运行时它只是原样返回对象、零开销、零副作用类型层面它通过 DataTag 把queryFn的数据类型「烙印」进queryKey让 Hook 与命令式 API 共享同一份类型安全Vue 适配层还额外支持ref/computed/ getter 形态的queryKey与enabled无缝融入响应式体系。无论是单一查询、useQueries批量查询还是getQueryData/setQueryData/prefetchQuery等命令式场景queryOptions都是值得优先使用的标准姿势。【免费下载链接】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),仅供参考