Deepdive into Tanstack/React Query - 2.2 QueryObserver 基本概念与订阅
2.2 QueryObserver 基本概念与订阅
QueryObserver 是 query-core 中连接底层数据状态 (Query) 与上层 UI 框架或订阅者的关键桥梁。它不直接获取数据,而是观察一个 Query 实例,并根据自身的配置选项计算出适合 UI 使用的结果状态 (QueryObserverResult),同时负责触发数据更新和通知订阅者。
2.2.1 职责与作用 (连接 Query 与订阅者)
QueryObserver 的核心职责是将 Query 的内部状态转化为外部消费者可以直接使用的 QueryObserverResult。其主要作用包括:
- 状态监听: 监听其关联
Query实例的状态变化。 (通过Query.addObserver)。 - 结果计算: 根据
Query状态和自身选项计算QueryObserverResult。 (核心在createResult方法)。 - 数据转换: 若配置了
select函数,在Query获取到新数据后进行转换。 - 行为触发: 根据选项和
Query状态决定何时触发数据获取 (fetch)。 - 订阅与通知: 管理订阅者 (
listeners),并在结果变化时通知。 - 生命周期管理: 管理自身订阅和与
Query的连接,清理资源。
以下是 QueryObserver 的完整类图表示:
注意:类图中以#开头的属性(如#client、#currentQuery)使用的是 ECMAScript 私有字段语法(ES2022),这是 TypeScript/JavaScript 中真正的运行时私有字段,外部完全无法访问,与传统的private关键字不同。
2.2.2 实例化与订阅/取消订阅 (subscribe, unsubscribe)
QueryObserver 继承自 Subscribable 类,实现了标准的发布-订阅模式。
- 实例化: 通过构造函数创建。
// packages/query-core/src/queryObserver.ts constructor( client: QueryClient, public options: QueryObserverOptions<...> ) { super() // 调用 Subscribable 的构造函数 this.#client = client // 保存 QueryClient 引用 this.#selectError = null this.#currentThenable = pendingThenable() // 初始化用于 Suspense 的 thenable // ... 初始化实验性特性检查 ... this.bindMethods() // 绑定 refetch 等方法到实例 this.setOptions(options) // 初始化选项并触发初始查询/结果计算 }源码解析: 构造函数接收
QueryClient
2.2.3 触发查询的基本方式
QueryObserver 主要通过调用内部的 #executeFetch() 方法来触发关联 Query 的数据获取。以下是常见的触发路径:
- 首次订阅 (Mount): 如
onSubscribe源码所示,当第一个订阅者加入且shouldFetchOnMount条件满足时,会调用#executeFetch()。// packages/query-core/src/queryObserver.ts function shouldFetchOnMount(query, options): boolean { return ( shouldLoadOnMount(query, options) || (query.state.data !== undefined && shouldFetchOn(query, options, options.refetchOnMount)) ) } function shouldLoadOnMount(query, options): boolean { return ( resolveEnabled(options.enabled, query) !== false && query.state.data === undefined && !(query.state.status === 'error' && options.retryOnMount === false) ) } function shouldFetchOn(query, options, field) { if ( resolveEnabled(options.enabled, query) !== false && resolveStaleTime(options.staleTime, query) !== 'static' ) { const value = typeof field === 'function' ? field(query) : field return value === 'always' || (value !== false && isStale(query, options)) } return false } function isStale(query, options): boolean { return ( resolveEnabled(options.enabled, query) !== false && query.isStaleByTime(resolveStaleTime(options.staleTime, query)) ) }
2.2.4 内部定时器管理 (#updateTimers)
QueryObserver 内部通过 #updateTimers 方法来协调管理两个重要的后台定时器:staleTimeout 和 refetchInterval。这个方法在关键的时机被调用(如 onSubscribe 和 onQueryUpdate),以确保定时器与当前的查询状态和观察者选项保持同步。
// packages/query-core/src/queryObserver.ts
#updateTimers(): void {
this.#updateStaleTimeout() // 更新数据过期定时器
this.#updateRefetchInterval(this.#computeRefetchInterval()) // 更新定期重新获取定时器
}
源码解析:
#updateTimers本身逻辑非常简单,它只是调用了两个更具体的内部方法:#updateStaleTimeout: 负责根据staleTime选项设置或清除一个超时计时器。当计时器到期时,如果结果尚未stale,它会调用updateResult来将结果标记为stale。#updateRefetchInterval: 负责根据refetchInterval选项启动、停止或更新一个周期性计时器。当计时器触发时,它会调用#executeFetch来执行后台数据刷新。
- 通过这种方式,
#updateTimers确保了QueryObserver能够根据最新的配置和状态来管理数据的新鲜度标记和自动重新获取行为。 - 这两个定时器的具体实现细节将在后续章节(3.2
staleTime和 5.1refetchInterval)中深入分析。