
QueriesObserver 深入解析TanStack Query 多查询观察器的架构原理与实战指南【免费下载链接】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/queryQueriesObserver 是 TanStack Query 框架核心中用于同时观察多个查询query的观察器类是useQueries、useSuspenseQueries等框架层 API 的底层引擎。本文将以其在query-core中的源码实现为主轴配合测试用例与框架层调用示例深入讲解 QueriesObserver 的创建、订阅、更新、去重、combine 合并等核心机制帮助你理解多查询场景下的内部工作流程并能直接在纯 TS/JS 场景中正确使用它。QueriesObserver 是什么在 TanStack Query 的架构中QueryObserver负责观察单个查询而QueriesObserver则是多个QueryObserver的协调器orchestrator它接收一个查询选项数组为每个查询维护一个内部的QueryObserver并把所有查询的结果聚合为一个结果数组对外发布。从源码可见其核心声明与泛型签名queriesObserver.tsexport interface QueriesObserverOptionsTCombinedResult ArrayQueryObserverResult { combine?: CombineFnTCombinedResult } export class QueriesObserverTCombinedResult ArrayQueryObserverResult extends SubscribableQueriesObserverListener { #client: QueryClient #result!: ArrayQueryObserverResult #queries: ArrayQueryObserverOptions #options?: QueriesObserverOptionsTCombinedResult #observers: ArrayQueryObserver // ... }它继承自Subscribablesubscribable.ts因此天然具备subscribe(listener)/hasListeners()等能力泛型TCombinedResult默认为ArrayQueryObserverResult即“不提供combine时直接返回查询结果数组”内部用私有字段维护QueryClient、查询列表、内部QueryObserver列表以及当前结果快照。QueriesObserver从tanstack/query-core包导出见 index.ts因此可以在任何框架React、Vue、Solid、Svelte、Angular、Lit 等之外独立使用。快速上手创建一个多查询观察器QueriesObserver的构造函数接收两个必填参数queryClient和查询选项数组以及一个可选的options目前只有combine。文档给出的最简示例QueriesObserver.mdconst observer new QueriesObserver(queryClient, [ { queryKey: [post, 1], queryFn: fetchPost }, { queryKey: [post, 2], queryFn: fetchPost }, ]) const unsubscribe observer.subscribe((result) { console.log(result) unsubscribe() })几点关键语义订阅即触发请求subscribe返回一个取消订阅函数一旦有监听者订阅内部所有QueryObserver都会开始订阅并触发各自的queryFn拉取数据。测试用例 “should trigger all fetches when subscribed” 验证了这一点queriesObserver.test.tsx订阅后两个queryFn各被调用一次。监听器收到的是结果数组回调参数result类型为ArrayQueryObserverResult每个元素对应传入的查询选项顺序保持一致。测试 “should return an array with all query results” 中订阅后结果为[{ data: 1 }, { data: 2 }]queriesObserver.test.tsx。多个订阅互不影响只要有任意一个监听者还在观察器就不会被销毁测试 “should not destroy the observer if there is still a subscription”见 queriesObserver.test.tsx。Options 说明与 useQueries 完全一致官方文档明确说明“The options for theQueriesObserverare exactly the same as those ofuseQueries.”useQueries的完整签名与选项说明见 useQueries.md。核心要点queries查询选项对象数组与useQuery的选项基本一致但有两个差异每个查询对象不接受queryClient选项QueryClient在顶层传入QueriesObserver构造函数subscribed不作为每个查询的选项而是顶层选项对QueriesObserver而言其等价语义是“是否订阅查询缓存更新”框架层通过控制是否调用observer.subscribe(...)实现placeholderData接收的是QueriesPlaceholderDataFunction其previousData/previousQuery参数恒为undefined——因为不同渲染之间查询数量可能不同无法像useQuery那样回填“上一次”的数据。combine(result: ArrayQueryObserverResult) TCombinedResult把多个查询的结果合并为单个值结果会做“结构共享”structural sharing尽可能保持引用稳定。返回值未提供combine时返回与输入顺序一致的结果数组提供combine时返回combine的返回值。在框架层useQueries正是通过new QueriesObserver(client, defaultedQueries, options)创建实例用observer.subscribe(notifyManager.batchCalls(onStoreChange))完成订阅并在每次渲染后调用observer.setQueries(defaultedQueries, options)同步最新的查询列表见 useQueries.ts。也就是说React 的useQueries只是 QueriesObserver 的一层薄封装。核心 API 逐项解析setQueries动态增删改查询列表setQueries(queries, options?)是 QueriesObserver 的“大脑”当查询列表变化数量或内容变化时用它来同步内部状态。实现要点queriesObserver.ts开发环境重复 key 告警它会对每个查询调用queryClient.defaultQueryOptions(query)计算queryHash若出现重复哈希则在开发环境打印警告[QueriesObserver]: Duplicate Queries found. This might result in unexpected behavior.。批处理整个同步过程包在notifyManager.batch(() { ... })中保证一次性通知。复用内部 observer通过#findMatchingObservers按queryHash匹配旧 observer能复用的就复用避免重复创建、保留查询缓存关联不能匹配的才新建QueryObserver。结构变化检测如果 observer 数量或位置发生变化hasStructuralChange或者某个结果对象发生浅比较变化hasResultChange才会触发后续更新否则直接返回避免无意义通知。测试 “should not update when nothing has changed” 验证了重复调用相同setQueries不会额外通知queriesObserver.test.tsx。订阅生命周期管理结构变化时对“退出”的 observer 调用destroy()对“新加入”的 observer 调用subscribe(...)挂钩更新回调。测试用例验证了增删查询的行为删除一个查询后对应查询从活跃缓存中消失结果数组同步缩短“should update when a query is removed”见 queriesObserver.test.tsx调整查询顺序后结果按新顺序排列“should update when a query changed position”queriesObserver.test.tsx已订阅状态下新增查询会自动订阅新 observer“should subscribe to new observers when a query is added while subscribed”queriesObserver.test.tsx。subscribe / unsubscribe订阅生命周期subscribe(listener)来自Subscribable基类把监听器加入Set首次订阅listeners.size 1时触发onSubscribe让内部所有QueryObserver开始订阅返回的取消函数删除监听器并在监听器清空时触发onUnsubscribe进而调用destroy()释放全部内部 observersubscribable.ts、queriesObserver.ts。getCurrentResult / getQueries / getObservers读取当前状态getCurrentResult()返回当前结果数组快照getQueries()返回内部所有QueryObserver当前关联的Query实例observer.getCurrentQuery()getObservers()返回内部QueryObserver数组。测试分别验证getQueries返回与输入 key 一致的查询queriesObserver.test.tsxgetObservers返回的每个元素都是QueryObserver实例queriesObserver.test.tsx。getOptimisticResult为渲染提供即时结果getOptimisticResult(queries, combine)是框架渲染路径的关键 API它不依赖已提交的订阅状态直接基于“理想匹配”的 observer 计算当前渲染应该展示的结果。它返回一个三元组rawResult每个匹配 observer 的getOptimisticResult(...)结果数组combineResult对结果数组执行combine的函数支持省略 raw 参数此时回退到已缓存结果见测试 “should use fallback result when combineResult is called without raw argument”trackResult用于属性级响应式跟踪的闭包记录本次渲染访问了哪些结果属性trackedProps并同步到所有 observer 上对应 issue #7000 的同步跟踪语义见 queriesObserver.ts。若某查询设置了notifyOnChangeProps则该查询跳过跟踪测试 “should return observer result directly when notifyOnChangeProps is set”queriesObserver.test.tsx。在 React 的useQueries中渲染前调用observer.getOptimisticResult(...)拿到三元组再交给useSyncExternalStore作为快照来源见 useQueries.ts。combine把多个查询结果合并成一个值combine是 QueriesObserver 最值得深入的功能。它的语义与实现要点用法示例来自 useQueries.mdfunction Posts({ ids }: { ids: Arraynumber }) { const { data, isPending, isError } useQueries({ queries: ids.map((id) ({ queryKey: [post, id], queryFn: () fetchPost(id), })), combine: (postQueries) { return { data: postQueries.map((query) query.data), isPending: postQueries.some((query) query.isPending), isError: postQueries.some((query) query.isError), } }, }) if (isPending) return Loading... if (isError) return Error loading posts // ... }实现原理queriesObserver.ts#combineResult会缓存上一次的combine函数引用与查询哈希列表只有当结果引用变化、查询哈希列表变化、或 combine 函数引用变化三者之一发生时才重新执行combine(input)并用replaceEqualDeep做深度结构共享保证“没变就不产生新引用”最大化渲染稳定性即使combine返回0、false、、null、NaN等 falsy 值缓存逻辑依然生效测试 “should cache the falsy combined result %s when nothing has changed” 逐一验证了这些边界值queriesObserver.test.tsx#shouldSkipCombine处理 Suspense 场景当任一查询处于suspense模式且data undefined时跳过 combine 通知避免渲染层收到不完整的合并结果测试 “should skip combine notifications while suspense queries have no data”queriesObserver.test.tsx。实践建议文档明确强调combine只在“引用变化”或“任一查询结果变化”时重跑因此内联的combine会在每次渲染时都执行——应使用useCallback包裹或提取为无依赖的稳定函数引用避免不必要的重复计算与重渲染。进阶类型层面的注意事项在 TypeScript 中使用useQueries/QueriesObserver时有一个已知的推断限制useQueries.md 明确说明useQueries一次性推断整个queries数组的类型因此内联对象中select参数的data无法从同对象的queryFn上下文推断会退化为unknown。解决办法有两种显式注解select参数类型用queryOptions()提前定义查询对象让类型在进入useQueries之前就被解析const postOptions (id: number) queryOptions({ queryKey: [post, id], queryFn: () fetchPost(id), }) function PostTitle({ id }: { id: number }) { const [{ data: fixed }] useQueries({ queries: [ queryOptions({ ...postOptions(id), // ✅ data 是 Post select: (data) data.title, }), ], }) return h1{fixed}/h1 }注意直接展开queryOptions的结果再内联覆盖select依然会退化为unknown需要再次用queryOptions包裹使覆盖在进入useQueries前完成解析。同一限制同样适用于useSuspenseQueries。另外不要在queries数组中重复同一个查询 key相同 key 会出现数据共享等非预期行为开发模式下 QueriesObserver 会打印重复 key 警告。官方建议先对查询去重、再把结果映射回所需结构。不过从源码看重复 key 在位置匹配机制下仍能正常工作且queryFn只执行一次测试 “should handle duplicate query keys in different positions” 验证了这一点queriesObserver.test.tsx但为语义清晰仍应避免。从源码结构看 QueriesObserver 的整体工作流综合 queriesObserver.ts 与测试可以将 QueriesObserver 的生命周期总结为构造保存QueryClient与options调用setQueries(queries)完成首次匹配匹配#findMatchingObservers按queryHash从旧 observer 池中复用或新建QueryObserver订阅subscribe → onSubscribe首个监听者加入时让所有内部 observer 订阅触发各查询拉取数据更新传播#onUpdate任一内部 observer 结果变化替换结果数组对应位置并通知监听者结构同步setQueries查询列表变化时复用/新建/销毁内部 observer并检测“无变化则跳过通知”合并combine可选地对结果数组做结构共享合并Suspense 未就绪时跳过释放destroy / onUnsubscribe最后一个监听者取消订阅时销毁所有内部 observer。这套机制为所有框架的多查询 API 提供了统一的、可预测的行为也让纯 TS/JS 项目可以脱离框架直接使用它来做服务端状态观察。总结QueriesObserver 是 TanStack Query 多查询能力的核心基础设施它封装了“多个 QueryObserver 的生命周期管理 结果聚合 结构共享合并”三大职责useQueries等框架 API 只是它的 React/Preact/Solid 等封装。掌握其构造参数、subscribe/setQueries/getOptimisticResult等核心方法与combine的缓存语义既能帮助你在纯 JS/TS 场景中直接使用它也能让你在使用useQueries时写出更高效、更稳定的代码。相关源码与测试可继续深入查阅 queriesObserver.ts 与 queriesObserver.test.tsx。【免费下载链接】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),仅供参考