Deepdive into Tanstack/React Query - 7. 框架集成
7.1 React 适配器解析
Tanstack Query 的核心设计是框架无关的,所有状态管理、缓存、重试等逻辑都在 @tanstack/query-core 中实现。React 适配器(@tanstack/react-query)的职责是将这些能力以符合 React 范式的方式暴露给开发者。
架构概览
💡 Tanstack Query 采用了适配器模式,将核心逻辑与框架特定代码分离,这使得 Vue、Solid、Svelte 等框架适配器可以复用同一套核心实现。
7.2 useBaseQuery Hook 实现
useBaseQuery 是所有查询 Hook 的基础,useQuery、useInfiniteQuery 等都是基于它构建的。
核心实现
关键设计点
1. Observer 的生命周期管理
使用 useState 的惰性初始化确保:
- Observer 只在组件挂载时创建一次
- 组件重新渲染时复用同一个 Observer 实例
- Observer 内部维护的订阅、缓存等状态得以保留
2. 订阅控制机制
这是一个重要的优化设计:
- 恢复期间不订阅:在 Hydration 场景下,
isRestoring为true时跳过订阅,避免不必要的更新 - 支持手动控制:通过
subscribed选项允许开发者完全禁用订阅(用于某些高级场景)
3. 乐观结果的获取时机
getOptimisticResult 必须在 useSyncExternalStore 之前调用,因为:
- 它会设置 Observer 的内部状态
- 确保首次渲染时能获取到正确的初始结果
- 为后续的订阅建立正确的基线
7.3 useSyncExternalStore 集成
useSyncExternalStore 是 React 18 引入的 Hook,专门用于订阅外部状态存储。Tanstack Query 利用它实现了与 React 的无缝集成。
为什么选择 useSyncExternalStore?
传统方式的问题:
- 撕裂(Tearing):在 React 并发模式下,同一次渲染中的不同组件可能读取到外部 store 的不同状态,导致 UI 不一致。
useSyncExternalStore通过在渲染阶段同步读取快照来解决这个问题 - 时序问题:
useEffect在渲染后执行,可能错过初始状态 - SSR 支持复杂:需要额外处理服务端渲染场景
useSyncExternalStore 的优势:
- 防止撕裂:确保并发渲染下状态一致
- 同步订阅:在渲染阶段就建立订阅
- SSR 友好:第三个参数提供服务端快照
集成实现细节
批量通知优化
observer.subscribe(notifyManager.batchCalls(onStoreChange))这里使用了 notifyManager.batchCalls 包装回调,确保:
- 多个状态变化合并为一次通知
- 与 React 的批量更新机制协同
- 避免不必要的重复渲染
7.4 Suspense 支持与 thenable
Suspense 工作原理
React Suspense 的核心机制是 throw promise:
Tanstack Query 的 Suspense 实现
1. 判断是否需要挂起
2. 抛出 thenable
thenable 的设计
thenable.ts 提供了一个关键的工具函数,用于创建符合 React Suspense 要求的对象:
为什么需要带状态的 thenable?
React 的 use Hook 和 Suspense 会检查 thenable 的 status 属性:
这种设计的优势:
- 避免重复请求:Promise resolve 后,状态变为
fulfilled,后续渲染直接读取值 - 同步读取:已完成的 thenable 可以同步返回结果
- 与 React 协作:符合 React Suspense 的期望接口
与 React 19 use() Hook 集成
TanStack Query v5 还支持通过 useQuery().promise 配合 React 19 的 use() Hook 使用:
这种方式的优势在于可以更灵活地控制 Suspense 边界,而不需要使用专门的 useSuspenseQuery。
7.5 Error Boundary 集成
throwOnError 配置
QueryErrorResetBoundary
Tanstack Query 提供了 QueryErrorResetBoundary 组件,用于与 Error Boundary 协同:
使用示例
7.6 experimental_prefetchInRender
这是一个实验性功能,允许在渲染阶段预取数据:
设计要点:
- 区分新旧缓存:新缓存条目需要立即发起请求,已有缓存则复用现有 Promise
- 组件卸载安全:即使组件在请求完成前卸载,请求也会继续完成
- 仅客户端:通过
!isServer确保只在客户端执行
7.7 属性追踪优化
trackResult 机制
当没有显式指定 notifyOnChangeProps 时,使用 trackResult 进行自动追踪:
优化效果
7.8 完整数据流
7.9 最佳实践
1. Suspense 模式配置
使用专门的 Suspense Hooks:useSuspenseQuery、useSuspenseInfiniteQuery、useSuspenseQueries。
2. 预加载数据
3. 并行查询与 Suspense
小结
Tanstack Query 的 React 适配器展示了如何将框架无关的核心逻辑优雅地集成到 React 生态中:
- useBaseQuery 作为所有查询 Hook 的基础,管理 Observer 生命周期
- useSyncExternalStore 提供了防止撕裂的状态同步机制
- thenable 模式 实现了与 Suspense 的无缝集成
- 属性追踪 自动优化渲染性能
- ErrorBoundary 集成 提供了声明式的错误处理
- experimental_prefetchInRender 探索了渲染阶段预取的可能性
这种设计既保持了核心逻辑的框架无关性,又充分利用了 React 的特性,是一个优秀的架构范例。