Deepdive into Tanstack/React Query - 6. 高级特性
深入解析 TanStack Query 的三大高级特性:NotifyManager 批量通知机制、Infinite Query 无限查询,以及 Hydration 服务端渲染支持。
6.1 NotifyManager 批量通知
NotifyManager 是 TanStack Query 内部用于协调和批量处理状态更新通知的核心模块。它确保多个状态变更能够被合并到单次渲染周期中,避免不必要的重复渲染。
核心设计
NotifyManager 维护了几个关键状态:
- queue: 待执行的回调队列
- transactions: 当前活跃的事务计数器
- notifyFn: 单个通知的执行函数
- batchNotifyFn: 批量通知的包装函数
- scheduleFn: 调度函数,默认使用
systemSetTimeoutZero(封装的setTimeout(fn, 0))
batch 批处理机制
batch 方法是 NotifyManager 的核心,它通过事务计数器实现嵌套批处理:
工作原理:
- 进入 batch 时,
transactions计数器递增 - 执行回调函数,期间所有
schedule调用都会将通知加入队列而非立即执行 - 退出时计数器递减,当归零时调用
flush()统一处理队列
这种设计支持嵌套调用,只有最外层的 batch 结束时才会真正触发通知。
schedule 调度策略
- 在事务中:回调被推入队列,延迟执行
- 不在事务中:通过
scheduleFn异步调度执行
flush 刷新队列
flush 的执行流程:
- 保存当前队列并清空(防止在执行过程中有新通知加入)
- 通过
scheduleFn异步调度 - 使用
batchNotifyFn包装所有通知,实现框架层面的批处理
与 React 调度的集成
TanStack Query 的 React 适配器会设置自定义的 batchNotifyFn:
在 React 18+ 中,unstable_batchedUpdates 已不再必要(自动批处理),但为了兼容性仍然保留。这确保了多个状态更新被合并为单次重渲染。
实际应用示例
在 Query 类的 #dispatch 方法中,我们可以看到 batch 的实际使用:
这确保了:
- 所有观察者的更新在同一批次中处理
- 缓存通知与观察者通知合并
- 最终只触发一次 React 重渲染
6.2 Infinite Query 无限查询
Infinite Query 是 TanStack Query 提供的用于处理分页数据的高级特性,特别适合实现"加载更多"和无限滚动场景。
InfiniteQueryObserver 扩展
InfiniteQueryObserver 继承自 QueryObserver,扩展了分页相关的功能:
核心扩展点:
- 重写
createResult以包含分页状态 - 提供
fetchNextPage和fetchPreviousPage方法 - 处理
InfiniteData结构
InfiniteData 数据结构
这种结构使得:
- 每次获取新页面时,数据被追加到
pages数组 pageParams记录了每页的请求参数,支持重新获取特定页
fetchNextPage / fetchPreviousPage
这两个方法通过设置 meta.fetchMore.direction 来控制获取方向:
setOptions 与 behavior 注入
InfiniteQueryObserver 在 setOptions 时会自动注入 infiniteQueryBehavior:
infiniteQueryBehavior 定义了分页数据的获取和合并逻辑,包括如何根据 direction 决定获取下一页还是上一页,以及如何将新数据合并到 InfiniteData 结构中。
页面参数计算
Infinite Query 使用 getNextPageParam 和 getPreviousPageParam 来计算下一页的参数:
返回 undefined 表示没有更多数据,这会将 hasNextPage 设为 false。
结果扩展
InfiniteQueryObserver 的 createResult 方法在父类结果基础上扩展了分页状态:
6.3 Hydration 服务端渲染
Hydration(水合)是 TanStack Query 支持服务端渲染(SSR)的核心机制,它允许在服务端预取数据并将其传递到客户端,避免客户端的重复请求。
核心概念
- Dehydrate(脱水):将 QueryClient 的状态序列化为可传输的 JSON
- Hydrate(水合):在客户端恢复序列化的状态
DehydratedState 数据结构
dehydrate 序列化状态
dehydrate 函数将 QueryClient 中的查询和变更状态提取为纯 JavaScript 对象:
dehydrateQuery 查询序列化
默认过滤条件
hydrate 恢复状态
hydrate 函数在客户端将序列化的状态恢复到 QueryClient:
SSR 最佳实践
1. Next.js App Router 集成
2. 服务端预取
3. HydrationBoundary 组件
注意事项
SSR 场景下建议设置 staleTime,否则数据会在客户端立即被标记为过期并重新获取,导致闪烁。
queryFn 不会被序列化,客户端必须提供相同的查询函数。确保服务端和客户端使用相同的 queryKey。
服务端和客户端的时间差可能影响 staleTime 判断,生产环境建议使用 NTP 同步时间。
注意不要序列化敏感数据到 HTML 中,shouldRedactErrors 可用于隐藏生产环境的错误详情。
小结
| 特性 | 核心作用 | 关键机制 |
| NotifyManager | 批量处理状态更新通知 | 事务计数器 + 队列 + 异步调度 |
| Infinite Query | 处理分页/无限滚动数据 | InfiniteData 结构 + 方向控制 |
| Hydration | 支持服务端渲染 | dehydrate 序列化 + hydrate 恢复 + promise streaming |
这三个高级特性共同构成了 TanStack Query 在复杂场景下的核心能力,理解它们的实现原理有助于更好地使用和调试应用。