新闻详情

新闻详情

首页 / 资讯中心 / 详情

TanStack Query QueryObserver 深入解析:观察者模式、订阅生命周期与 useQuery 底层实现

发布时间:2026/9/10 6:50:42来源:尧图网络
TanStack Query QueryObserver 深入解析:观察者模式、订阅生命周期与 useQuery 底层实现
TanStack Query QueryObserver 深入解析观察者模式、订阅生命周期与 useQuery 底层实现【免费下载链接】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/queryQueryObserver是 TanStack Query 中负责观察并切换查询的核心类它把查询缓存中的底层状态包装成组件友好的结果对象并通过订阅机制驱动 UI 更新。无论你用的是 React Query、Solid Query、Svelte Query 还是 Vue Query所有useQuery类 API 最终都建立在QueryObserver之上。读完本文你将掌握QueryObserver的构造与订阅用法、它与useQuery的等价选项关系、订阅生命周期内部机制、结果派生与性能优化手段以及它如何通过属性跟踪和notifyOnChangeProps减少无谓渲染。QueryObserver 是什么按照 docs/reference/QueryObserver.md 的定义QueryObserver可以用来观察observe查询并在查询之间切换switch。它是 TanStack Query 架构中查询缓存QueryCache与视图层UI之间的桥梁。一个QueryObserver实例持有对QueryClient的引用和一个QueryObserverOptions配置通过订阅机制监听查询状态变化再把变化整理成QueryObserverResult结果对象分发给所有监听者。从源码结构看QueryObserver继承自SubscribableTListener见 subscribable.ts后者用SetTListener维护监听器列表并提供三个关键能力subscribe(listener)注册监听器返回一个用于退订的unsubscribe函数hasListeners()判断当前是否还有监听者受保护的onSubscribe()/onUnsubscribe()钩子在首个订阅者加入或最后一个订阅者离开时触发由QueryObserver覆写以驱动真实的获取逻辑。QueryObserver覆写了这两个钩子onSubscribe()在第一个监听者加入时把自身注册到当前查询#currentQuery.addObserver(this)并在需要时触发首次请求shouldFetchOnMountonUnsubscribe()在没有监听者时调用destroy()清理定时器并从查询上移除自己。这就是订阅即获取、退订即清理的生命周期基础。快速上手直接实例化与订阅QueryObserver可以直接脱离任何框架使用只需传入一个QueryClient和查询选项。原文档给出的最小示例见 docs/reference/QueryObserver.mdconst observer new QueryObserver(queryClient, { queryKey: [posts] }) const unsubscribe observer.subscribe((result) { console.log(result) unsubscribe() })这里queryClient通常来自new QueryClient()subscribe的回调会在每次查询结果变化时收到一个QueryObserverResult对象且subscribe立即返回退订函数。上面的例子在收到第一次通知后就主动退订适合一次性观察。一个更完整的可用示例结合 queryObserver.ts 的构造函数签名import { QueryClient, QueryObserver } from tanstack/query-core const queryClient new QueryClient() const observer new QueryObserver(queryClient, { queryKey: [posts], queryFn: () fetch(/api/posts).then((res) res.json()), staleTime: 5_000, }) const unsubscribe observer.subscribe((result) { console.log(result.status, result.data, result.isFetching) }) // 观察结束或组件卸载时退订 unsubscribe()构造QueryObserver时queryObserver.ts构造函数会做三件事保存client引用调用bindMethods()绑定refetch方法保证它被取出后this依然指向 observer调用setOptions(options)完成选项默认化、查询实例绑定和结果初始化。也就是说仅仅new QueryObserver(...)还不会发起请求。真正触发请求的时机有两个一是调用subscribe且成为第一个订阅者onSubscribe中执行shouldFetchOnMount判断二是显式调用refetch()或fetch()。测试 queryObserver.test.tsx 中should trigger a fetch when subscribed验证了这一点订阅后queryFn恰好被调用一次。Options与 useQuery 完全相同的配置原文档明确指出QueryObserver的 Options 与useQuery完全一致原文档链接指向 useQuery。这并非巧合而是设计使然useQuery在 React 侧只是一个薄封装它把用户传入的 options 原样交给QueryObserver。见 useQuery.tsreturn useBaseQuery(options, QueryObserver, queryClient)而 useBaseQuery.ts 中正是用这些 options 实例化 observerconst [observer] React.useState( () new ObserverTQueryFnData, TError, TData, TQueryData, TQueryKey( client, defaultedOptions, ), )因此useQuery 文档中列出的所有选项——queryKey、queryFn、enabled、staleTime、gcTime、refetchInterval、refetchOnWindowFocus、refetchOnReconnect、refetchOnMount、retry、retryOnMount、placeholderData、initialData、select、structuralSharing、notifyOnChangeProps、throwOnError、suspense、_optimisticResults等——都适用于QueryObserver。核心选项可归纳为以下几类类别选项作用数据定位queryKey、queryHash定位/标识缓存中的查询数据获取queryFn返回 Promise 的请求函数开关控制enabled布尔值或返回布尔值的回调控制是否启用查询新鲜度staleTime数据被视为新鲜的时长影响自动重取与 stale 判定缓存gcTime无订阅者后缓存留存时间重取策略refetchOnMount、refetchOnWindowFocus、refetchOnReconnect、refetchInterval、refetchIntervalInBackground各种场景下的自动重取规则错误处理retry、retryDelay、retryOnMount、throwOnError重试与错误抛出策略数据变换select、structuralSharing派生数据与结构共享初始/占位initialData、placeholderData无真实数据时展示的数据渲染优化notifyOnChangeProps、trackedProps控制通知与属性跟踪框架集成suspense、_optimisticResultsReact Suspense 与乐观结果这些选项在setOptions中会被queryClient.defaultQueryOptions(options)处理为默认化defaulted配置并经过合法性校验——例如enabled必须是布尔值或返回布尔值的回调否则直接抛出Expected enabled to be a boolean or a callback that returns a booleanqueryObserver.ts。订阅生命周期从 onSubscribe 到 destroyQueryObserver的生命周期由订阅驱动其核心脉络记录在 queryObserver.ts 中可用下图概括new QueryObserver(client, options) │ ▼ setOptions(options) ──► defaultQueryOptions / updateQuery / updateResult │ ▼ subscribe(listener) ──► Subscribable.subscribe │ │ │ listeners.size 1 ? │ │ │ ┌────────┴────────┐ │ ▼ ▼ │ currentQuery.addObserver(this) │ shouldFetchOnMount ? executeFetch : updateResult │ updateTimers() ▼ query 更新 ──► onQueryUpdate() ──► updateResult() updateTimers() │ ▼ unsubscribe() ──► 无监听者 ? destroy() : 保留继续观察 │ ▼ destroy() ──► 清空 listeners / clear stale refetch timers / removeObserver各阶段的关键行为首次订阅onSubscribequeryObserver.ts仅当listeners.size 1即第一个订阅者时把 observer 注册到当前查询随后根据shouldFetchOnMount决定是立即#executeFetch()还是仅updateResult()最后调用#updateTimers()启动 stale 超时与refetchInterval定时器。选项更新setOptionsqueryObserver.ts重新默认化选项、必要时切换查询实例#updateQuery、向 QueryCache 发送observerOptionsUpdated通知并在满足shouldFetchOptionally时触发可选重取最后重建结果与定时器。测试should notify cache listeners when setOptions is calledqueryObserver.test.tsx验证了选项变更会通知缓存监听者。查询状态更新onQueryUpdatequeryObserver.ts查询数据变化时由 Query 回调触发先updateResult()通知监听者再同步更新定时器。退订与销毁onUnsubscribe→destroyqueryObserver.ts当最后一个监听者离开时清空listeners、清除 stale 超时与重取间隔借助timeoutManager并调用#currentQuery.removeObserver(this)从查询上摘除自己。测试should stop retry when unsubscribingqueryObserver.test.tsx表明退订还会终止进行中的重试。值得注意的实现细节stale 超时使用了timeoutManager.setTimeout并且源码注释说明超时有时会在 stale 过期前 1ms 触发因此统一加 1ms 补偿queryObserver.tsrefetchInterval定时器则在触发前检查refetchIntervalInBackground || focusManager.isFocused()只有页面处于焦点时才在后台重取queryObserver.ts。在查询之间切换QueryObserver的另一个核心职责是在查询之间切换。这通过#updateQuery()queryObserver.ts实现#updateQuery(): void { const query this.#client.getQueryCache().build(this.#client, this.options) if (query this.#currentQuery) { return } const prevQuery this.#currentQuery this.#currentQuery query this.#currentQueryInitialState query.state if (this.hasListeners()) { prevQuery?.removeObserver(this) query.addObserver(this) } }切换逻辑非常明确用新 options 通过QueryCache.build解析出或复用缓存中已有的目标 Query若与当前查询不同则记录新查询并在有监听者时从旧查询移除自己、加入新查询。也就是说同一个 observer 实例可以持续观察多个 queryKey——当setOptions传入新的queryKey时观察目标自动切换。测试should notify when switching queryqueryObserver.test.tsx验证了切换时监听者会收到新查询的结果通知。此外observer 内部维护#lastQueryWithDefinedData记录最近一次有数据的查询用于在切换后为placeholderData函数提供上一份数据queryObserver.ts这正是分页/详情页切换时保持旧数据可见能力keepPreviousData的底层来源。结果派生createResult 如何组装 QueryObserverResultcreateResultqueryObserver.ts是QueryObserver最核心的纯计算函数它接收一个 Query 实例和 options产出一个完整的QueryObserverResult。其派生过程按顺序包含基础状态从query.state复制statuspending/success/error、fetchStatusidle/fetching/paused、data、error等乐观结果当options._optimisticResults存在时根据shouldFetchOnMount/shouldFetchOptionally提前把状态置为fetching若为isRestoring则强制fetchStatus idleplaceholderData 处理在data undefined status pending时若配置了placeholderData则计算占位数据并把status提升为success、标记isPlaceholderData trueplaceholderData可以是值或函数函数会收到最近一次有数据查询的data与query参数queryObserver.tsselect 派生若配置了select对含占位数据执行选择器结果通过replaceData内部依赖structuralSharing做结构共享和#selectResult缓存做记忆化数据与选择器未变时不会重复执行对应测试should not run the selector again if the data and selector did not changequeryObserver.test.tsxselect抛错会被捕获进#selectError并转化为status: error且切换查询后会清空避免错误泄漏派生布尔量最终结果包含isPending、isSuccess、isError、isLoading、isInitialLoading、isFetching、isRefetching、isPaused、isPlaceholderData、isFetched、isFetchedAfterMount、isLoadingError、isRefetchError、isStale、isEnabled以及refetch方法、dataUpdatedAt、errorUpdatedAt、failureCount、failureReason等完整字段。isLoading isPending isFetching这一派生关系值得特别说明当查询被enabled: false禁用时fetchStatus为idle因此isLoading为false这正是官方推荐禁用场景下用isLoading而非isPending的原因见 useQuery 中的依赖查询示例。staleTime特殊值static也在源码中有对应逻辑shouldFetchOn中当staleTime为static时直接返回false即静态数据永远不会被自动重取queryObserver.ts。属性跟踪与通知优化notifyOnChangeProps 与 trackResultQueryObserver内置了一套渲染优化机制避免任何字段变化都触发所有监听者属性跟踪trackResult用Proxy包装结果对象读取任何属性时都会记录到#trackedPropsqueryObserver.ts。在 React 中useBaseQuery在没有notifyOnChangeProps时返回observer.trackResult(result)useBaseQuery.ts因此只有组件实际读取过的属性才会计入跟踪集合。按需通知updateResult中的shouldNotifyListeners()queryObserver.ts决定是否真的调用监听者notifyOnChangeProps为all时全量通知为函数时取其返回值作为允许通知的属性集合否则使用#trackedProps。只有当前结果中确实变化了的属性命中了允许集合才会触发通知。测试should notify listeners when notifyOnChangeProps is a function returning props that changed与should not notify listeners when notifyOnChangeProps is a function returning props that did not changequeryObserver.test.tsx正反两面验证了该行为。批量通知所有监听者回调与 QueryCache 的observerResultsUpdated通知被包裹在notifyManager.batch()中实现一次渲染周期内的合并通知queryObserver.ts。浅比较短路updateResult在shallowEqualObjects(nextResult, prevResult)为真时直接返回不做任何通知queryObserver.ts。手动获取refetch、fetch 与 fetchOptimistic除了订阅驱动QueryObserver还提供命令式获取能力refetch(options?)queryObserver.ts返回PromiseQueryObserverResult是结果对象上refetch字段的实现。cancelRefetch默认在源码fetch路径中被置为truequeryObserver.ts即重取会取消进行中的旧请求。fetch(options?)受保护的内部方法调用#executeFetch后立即updateResult()并返回最新结果。fetchOptimistic(options)queryObserver.ts为 Suspense 场景设计——先在缓存中build出查询并发起query.fetch()同时订阅 QueryCache 等待数据落库最后用Promise.race取两者中先完成的结果。React 侧的useSuspenseQuery正是通过fetchOptimistic在 Suspense 抛出前预先获取数据suspense.ts。getOptimisticResult(options)queryObserver.ts在不订阅的情况下用给定 options 模拟计算结果供 React 渲染期提前获得乐观状态。useBaseQuery在useSyncExternalStore之前就调用它来获取渲染结果useBaseQuery.ts随后才订阅。useBaseQuery中还有一处细节印证了updateResult的必要性订阅建立后立即调用一次observer.updateResult()以补上创建 observer 与订阅之间可能错过的查询更新useBaseQuery.ts。禁用与定时器的协同QueryObserver对enabled的处理贯穿整个实现禁用enabled: false时shouldLoadOnMount/shouldFetchOn/shouldFetchOptionally/isStale均短路返回false因此不会自动请求、不会判定为 stale#shouldScheduleTimer要求enabled ! false且非服务端环境才允许调度定时器queryObserver.ts测试should not schedule timers for disabled observersqueryObserver.test.tsx确认禁用 observer 不启动任何定时器。注意enabled支持回调形式如() enabled每次判定时通过resolveQueryValue求值使选项在函数上下文中也能动态计算。测试中enabled is a callback that initially returns false组queryObserver.test.tsx验证了回调返回false时订阅也不会发起请求。多订阅者与框架集成QueryObserver天然支持多监听者Subscribable.listeners是SetupdateResult会遍历所有监听者分发同一份#currentResult。测试should be able to handle multiple subscribersqueryObserver.test.tsx验证了这一点。多个组件订阅同一个 queryKey 时它们共享缓存中的同一个 Query 实例但各自持有独立的QueryObserver互不干扰。各框架层的集成路径清晰一致ReactuseQuery→useBaseQuery(options, QueryObserver, ...)useQuery.tsReact 多查询useQueries在内部直接new QueryObserver(client, opts)管理一组观察者useQueries.ts聚合观察QueriesObserverqueriesObserver.ts同样是基于QueryObserver的组合器用于useQueries的框架无关实现其他框架Solid Query、Svelte Query、Vue Query 以及 Lit 的createQueryControllerlit-query/src/createQueryController.ts均以QueryObserver为底层只是订阅到各自响应式系统的桥接方式不同。正因如此QueryObserver的选项、生命周期与结果结构在所有框架中保持一致——这也解释了为什么文档直接说Options 与useQuery完全相同。小结QueryObserver是 TanStack Query 中承上启下的核心构件观察与切换一个实例可随 options 变化在多个查询间切换订阅驱动获取与清理见 queryObserver.ts选项统一Options 与 useQuery 完全一致框架层只是薄封装结果派生createResult集中处理 placeholderData、select、结构共享与全部派生布尔量性能优化trackResult属性跟踪 notifyOnChangeProps按需通知 notifyManager.batch批量通知让监听者只在真正关心的字段变化时被唤醒可测试性queryObserver.test.tsx 用近 2000 行测试覆盖了订阅触发、查询切换、选择器记忆化、禁用定时器、退订停止重试、后台重取开关等全部关键路径是理解其行为边界的最佳参考。无论是排查useQuery的重取行为还是需要脱离框架在 Node/脚本环境中监听查询状态QueryObserver都是值得直接使用的底层 API。【免费下载链接】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),仅供参考
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Spring Boot接入AI搜索:向量化、RAG链路与调参实战指南 2026/9/10 7:29:48

Spring Boot接入AI搜索:向量化、RAG链路与调参实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
2026 AI编程四维决策框架:补全、Agent、隐私与成本的工程平衡 2026/9/10 7:29:48

2026 AI编程四维决策框架:补全、Agent、隐私与成本的工程平衡

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
CVAT 完整指南:30 分钟跑通数据标注平台的部署与团队协作 2026/9/10 7:29:48

CVAT 完整指南:30 分钟跑通数据标注平台的部署与团队协作

CVAT 完整指南:30 分钟跑通数据标注平台的部署与团队协作 【免费下载链接】cvat Computer Vision Annotation Tool (CVAT) is a leading platform for building high-quality visual datasets for vision AI. It offers open-source, cloud, and enterprise product…

阅读更多 →
中断上半部响应时间优化实战:从裸机到RTOS的完整指南 2026/9/10 7:29:48

中断上半部响应时间优化实战:从裸机到RTOS的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
风光互补制氢合成氨系统容量与调度双层优化Matlab实现 2026/9/10 7:29:48

风光互补制氢合成氨系统容量与调度双层优化Matlab实现

1. 项目整体思路与优化框架拆解1.1 这个系统到底在优化什么先把项目名字拆开看。风光互补制氢合成氨,这条链路上涉及三个核心环节:发电侧(风机光伏)、制氢侧(电解槽)、合成氨侧(氨合成塔&#x…

阅读更多 →
JS三大拦路虎:闭包、原型链与事件循环面试深度解析 2026/9/10 7:26:48

JS三大拦路虎:闭包、原型链与事件循环面试深度解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞