ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Vue Query 动态查询批处理实战:useQueries 完全指南

Vue Query 动态查询批处理实战:useQueries 完全指南 Vue Query 动态查询批处理实战useQueries 完全指南【免费下载链接】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/queryuseQueries是 Vue Query本仓库中位于 packages/vue-query提供的用于同时监听一组数量可变查询的组合式 APIcomposable。当页面需要根据列表、ID 数组或分页数据动态渲染多个独立查询时例如帖子列表、批量用户详情、多图表仪表盘useQuery只能逐个固定调用而useQueries让你一次性声明整组查询并拿到与输入顺序一致的结果数组。读完本文你将掌握useQueries的完整用法、combine结果合并、Vue 响应式与函数式 queries 的写法以及select类型推断的已知局限与两种绕行方案。useQueries 是什么与 useQuery 的定位差异useQueries的核心价值在于动态数量调用时传入的查询对象数组长度可以在每次渲染时变化而 useQuery 对应的是一个固定查询。底层上useQueries使用tanstack/query-core中的QueriesObserver来统一管理一组QueryObserver见 packages/query-core/src/queriesObserver.ts并通过#findMatchingObservers按queryKey复用已有的 observer从而在查询数量变化时尽可能保留缓存与订阅状态。Vue Query 中useQueries的完整实现位于 packages/vue-query/src/useQueries.ts其签名可以简化为export function useQueriesT extends Arrayany, TCombinedResult UseQueriesResultsT( { queries, // 查询对象数组或其响应式版本/工厂函数 combine, // 可选把结果数组合并成单一值 }: ShallowOption { queries: ...; combine?: ... }, queryClient?: QueryClient, ): ReadonlyRefTCombinedResult最基础的用法是数据 数量都不固定的场景——最常见的是ids.map(...)const ids [1, 2, 3] const results useQueries({ queries: ids.map((id) ({ queryKey: [post, id], queryFn: () fetchPost(id), staleTime: Infinity, })), }) // results 是一个 readonly ref —— 需要解包访问数组 results.value注意返回值useQueries返回的是readonly ref数组存在.value上且元素顺序与传入的queries数组顺序完全一致测试用例见 packages/vue-query/src/tests/useQueries.test.ts 中 should return result for each query 与 should resolve to success and update reactive state 两个用例分别验证了初始 pending 状态与成功后按序解析为 success。模板中通过results.value或results[0].data的解构形式即可读取。底层响应式shallowRef getOptimisticResult从源码看Vue Query 的useQueries并非把整个结果数组成ref深层代理而是用computed对queries做默认化client.defaultQueryOptions并对每个查询对象执行cloneDeepUnref深度解包 ref源码位于 packages/vue-query/src/utils.ts创建QueriesObserver通过observer.getOptimisticResult(...)在首帧同步得到乐观结果将状态放入shallowRef订阅 observer 更新组件卸载时通过onScopeDispose取消订阅防止内存泄漏这也是文档与测试中强调useQueries必须在setup()或 effect scope 内调用的原因——开发模式下在 setup 之外调用会打印警告。Optionsqueries、queryClient 与 combineuseQueries接受一个 options 对象其中queries键的值是一个数组数组元素是查询配置对象与useQuery的选项完全相同唯一例外是不包含queryClient选项——因为QueryClient可以在顶层传入。queryClient?: QueryClient用途显式提供一个自定义的QueryClient不传时使用最近上下文useQueryClient()中的那个。测试佐证useQueries.test.ts 的 should use queryClient provided via options 用例验证了传入第二参数后不会再调用useQueryClient。它同时也是useQueries的第二个位置参数useQueries(options, queryClient)与useQuery的签名保持一致。combine?: (result: UseQueriesResults) TCombinedResult用途把多个查询的结果合并为单个值详见下文 Combine 章节。queries 的三种形态queries可以是普通数组字面量上例响应式数据ref/computed/ 深层嵌套 ref甚至queryKey内部可以嵌套 getter 函数工厂函数queries: () [...]用于完全以函数形式动态生成。测试用例 should return state for new queries 演示了将queries放在ref中、在运行时splice改变查询数量后结果数组长度与数据都会随之响应式更新should allow getters for query keys 与 should allow arbitrarily nested getters for query keys 则验证了 queryKey 中嵌套 ref 与任意层级 getter数组、对象、() ({ foo: { bar: () ... } })都能被深度解包并触发重新请求。这些都得益于cloneDeepUnref在 utils.ts 中对queryKey专门做了递归求值level 1 key queryKey时unrefGetters true。// 响应式数量 响应式 key 的混合写法 const ids ref([1, 2, 3]) const results useQueries({ queries: () ids.value.map((id) ({ queryKey: [post, () id], queryFn: () fetchPost(id), })), })注意queries数组中同一个 queryKey 出现多次会导致查询之间的数据共享。若需要每个 key 一份独立数据应当先对查询去重再把结果映射回你期望的结构。placeholderData 在 useQueries 中的差异placeholderData选项在useQueries中同样存在但它无法像useQuery那样接收上一次渲染的查询信息。原因正如文档所述useQueries每次渲染的查询数量都可能不同不存在前一次渲染的对应查询这一概念。在类型层面这个差异也被固化进了源码useQueries.ts顶部专门定义了UseQueryOptionsForUseQueries其中明确注释 placeholderDatafunction does not have a parameter——即这里placeholderData的函数签名没有参数。因此不要在useQueries里依赖placeholderData的previousData参数来实现用上一次数据占位。返回值readonly ref 的结果数组useQueries返回一个 readonly ref.value是包含所有查询结果的数组顺序与输入一致。数组中每个元素与useQuery返回的QueryObserverResult结构相同包含data、error、status、isPending、isFetching、isStale、refetch等字段。refetch与useQuery不同useQueries结果数组中每个元素的refetch是按索引绑定到对应查询的。源码中getOptimisticResult对每个结果注入了refetch: async (...args) observer 中该 index 的 query.refetch(...args)。测试佐证用例 should refetch only the specific query without affecting others 验证了queriesState.value[0].refetch()只重取第一个查询第二个查询的数据保持不变。数组元素是引用稳定的Vue Query 底层通过QueriesObserver的getOptimisticResult同步返回结果状态存入shallowRef因此结果对象本身不会在无关数据变更时被整体替换。Combine把多个查询结果合并为单一值如果你想把多个查询的data或其它查询信息合并成一个值可以使用combine选项。合并结果会经过**结构性共享structural sharing**处理尽量保证引用稳定——底层QueriesObserver.#combineResult使用replaceEqualDeep对合并结果做深度相等比较只有内容变化才替换引用见 packages/query-core/src/queriesObserver.ts。const ids [1, 2, 3] const combinedQueries useQueries({ queries: ids.map((id) ({ queryKey: [post, id], queryFn: () fetchPost(id), })), combine: (results) { return { data: results.map((result) result.data), pending: results.some((result) result.isPending), } }, })如上例所示combinedQueries.value将是一个包含data和pending属性的对象。注意查询结果的所有其他属性error、status等都会丢失——combine的返回值完全替代了原始结果数组。combine的类型同样参与推断useQueries的泛型TCombinedResult默认是UseQueriesResultsT一旦提供combine返回 ref 的值类型就变成combine的返回类型。类型测试useQueries.test-d.ts中queryCombine用例验证了combine返回Arraystring时.value类型即为Arraystring。Memoization记忆化规则combine函数只会在以下情况重新执行combine函数本身发生了引用变化任一查询结果发生了变化。与 React 不同Vue 的setup()每个组件实例只执行一次因此像上文那样内联书写的combine函数在响应式更新期间引用是稳定的无需额外记忆化处理。即便combine内引用了外部响应式状态也只有该状态变化真正影响查询结果时才会触发重算——因为核心触发条件仍然是查询结果变化。而底层QueriesObserver还会做双保险#combineResult中对比queryHashes是否变化并结合replaceEqualDeep保证每次返回引用稳定的合并结果避免无谓的组件重渲染。TypeScript给 select 标注类型已知局限与绕行方案与useQuery不同useQueries无法从内联select的兄弟queryFn推断出data参数类型。原因在于useQueries是一次性推断整个queries数组的类型见源码中的UseQueriesOptions/UseQueriesResults递归类型推导数组元素对象内联书写时select的参数无法从同一对象里的queryFn做上下文推断因此回退为unknown。这是 TypeScript 的一个已知限制TanStack Query issue #6556 讨论的同源问题。useQueries({ queries: [ { queryKey: [post, 1], queryFn: () fetchPost(1), // ❌ data 在这里是 unknown select: (data) data.title, }, ], })有两种受支持的绕行方案方案一显式标注select参数类型useQueries({ queries: [ { queryKey: [post, 1], queryFn: () fetchPost(1), // ✅ data 是 Post select: (data: Post) data.title, }, ], })方案二先用 queryOptions 定义查询再传入 useQueriesqueryOptions 帮助函数会在对象到达useQueries之前就独立解析其类型在 queryOptions.ts 中它只是一个透明的身份函数纯粹用于类型收窄因此内部的select可以正常从queryFn推断const postOptions (id: number) queryOptions({ queryKey: [post, id], queryFn: () fetchPost(id), // ✅ data 是 Post select: (data) data.title, }) useQueries({ queries: [postOptions(1), postOptions(2)] })类型测试对这两种形态都有覆盖useQueries.test-d.ts 中 should be possible to define a different TData than TQueryFnData using select with queryOptions spread into useQueries 验证了queryOptionsselect的data推断而内联对象则必须手动标注(data: any)。同样的局限也适用于展开queryOptions结果再内联覆盖select的场景——覆盖后的select依然回退为unknownuseQueries({ queries: [ { ...postOptions(1), // ❌ data 在这里是 unknown select: (data) data.title, }, ], })正确做法把展开结果再次包进queryOptions让覆盖后的select在到达useQueries之前就完成类型解析useQueries({ queries: [ queryOptions({ ...postOptions(1), // ✅ data 是 Post select: (data) data.title, }), ], })更多类型推断细节除了selectuseQueries的类型推导还会根据initialData调整data类型DefinedQueryObserverResultvsQueryObserverResult。类型测试中提供了对象形式initialData的查询data类型为确定值如{ wow: boolean }initialData是可能返回undefined的函数时data类型包含undefined条件式skipTokenMath.random() 0.5 ? skipToken : () Promise.resolve(5)也能推断出QueryObserverResultnumber, Error。这些行为由 useQueries.ts 中的GetDefinedOrUndefinedQueryResult与GetUseQueryResult类型工具保证并且通过MAXIMUM_DEPTH 20防止超大数组字面量触发 TS 深度限制错误。完整实战示例动态帖子列表 合并聚合综合以上知识一个典型的实战场景——根据用户选择的多个帖子 ID 拉取数据并在加载时展示整体 pending 状态script setup langts import { ref } from vue import { useQueries } from tanstack/vue-query import { fetchPost } from ./api const ids refnumber[]([1, 2, 3]) const posts useQueries({ queries: () ids.value.map((id) ({ queryKey: [post, id], queryFn: () fetchPost(id), // 显式标注绕过 select 推断局限 select: (post: Post) post.title, })), combine: (results) ({ titles: results.map((r) r.data), isPending: results.some((r) r.isPending), hasError: results.some((r) r.isError), }), }) // 在模板中使用 posts.value.titles / posts.value.isPending /script要点回顾ids变化增删改时queries: () ...工厂函数会重新计算QueriesObserver按 queryKey 复用/新增/移除 observercombine输出聚合对象配合replaceEqualDeep的结构性共享避免无关渲染内联select记得标注参数类型或用queryOptions包裹。更完整的可运行示例可以继续参考仓库中的 examples/vue 目录如basic、dependent-queries等示例以及 useQueries 单元测试 与 类型测试 来对照验证行为。【免费下载链接】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),仅供参考
返回列表