TanStack DB:让仪表盘实现即时响应的响应式客户端存储

TanStack DB 完整指南:深入解析实时查询、乐观更新与亚毫秒级差分数据流响应。BETA 版本,周下载量约 300 万次,支持 Eager/On-demand/Progressive 三种同步模式,提供实际生产环境示例与完整代码演示,深入对比 RxDB、替代方案。

Huifer
Huifer
September 18, 202610 min read
其他语言:Deutsch · English

本文由 Huifer 撰写,TanStack Ship 的独立开发者和维护者。 自 2024 年以来,我已在 Cloudflare Workers 上构建了 12+ 个 SaaS 仪表盘。遇到的最严重的性能问题是计费分析仪表盘——每次变更后需要刷新 8 种过滤组合,导致发起 8 次重新获取请求、等待 8 次网络响应、渲染 8 个加载状态。2026 年 3 月,我用 TanStack DB 重写了它。现在每次变更后,仪表盘在 1ms 内更新全部 8 个面板,且在任何网络请求发出之前。重新获取在后台进行。用户在看过滤变更时永远不会看到加载动画。这一经历塑造了我对客户端响应式数据的思考方式——也是 TanStack DB 成为数据密集型仪表盘理想工具的原因。

已验证来源:TanStack DB Overview · TanStack DB on npm · TanStack DB 0.6 公告 · ElectricSQL + TanStack DB · TanStack Store 最后更新:2026-09-18 · 更新日志

TL;DR: TanStack DB (@tanstack/db,Beta,约 300 万周下载量) 是一个响应式客户端存储,可在 1ms 内对现有 API 运行实时查询。Collection + 实时查询 + 乐观更新 = 仪表盘、Kanban 和任何写入需要即时感知的 UI 的最佳模式。


为什么标准状态管理在仪表盘规模下会失效

大多数 React 应用的心智模型是:写入 → API 调用 → 重新获取 → 重新渲染。对于简单应用,这样没问题。

对于一个有 8 个过滤器、4 个汇总面板和 1 个数据表的仪表盘,一次过滤变更会触发 8 次 API 调用。用户看到 8 个加载动画。数据陆续到达。部分面板在数据到达前就开始渲染。布局随着每个面板填充内容而不断跳动。

你尝试乐观更新。TanStack Query 的 onMutate + onError 回滚模式一次只处理一个变更。对于仪表盘——一个操作更新三个相关资源(订单、收入和库存)——你需要三条独立的乐观更新流程,且当其中一个失败时全部保持一致。这就是 onMutate/onError/onSettled 变成 60 行共享回滚状态乱麻的原因。

TanStack DB 是 TanStack 团队给出的答案。它位于现有 REST 或 GraphQL API 前端,将数据保存在类型化 Collection 中,运行响应式查询以增量方式重新计算——不是重新运行整个查询,而是仅应用变更的行。在 M1 级芯片上,10 万行 Collection 中的更新涟漪约需 0.7ms。


三个原语:Collection、实时查询、变更

TanStack DB 有三个核心组件。掌握这三个,完整模型就豁然开朗。

Collection:类型化数据镜像

Collection 是由数据源支持的类型化对象集合。最常见的支持数据源是 TanStack Query 的 queryFn——你现有的获取逻辑直接可以复用:

typescript
import { createCollection } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'

const ordersCollection = createCollection(
  queryCollectionOptions({
    queryKey: ['orders'],
    queryFn: () => fetch('/api/orders').then(r => r.json()),
    getKey: (order) => order.id,
    onUpdate: async ({ transaction }) => {
      const updates = transaction.mutations.map(({ key, changes }) => ({
        id: key,
        ...changes,
      }))
      await fetch('/api/orders/batch', {
        method: 'PATCH',
        body: JSON.stringify(updates),
      })
    },
  }),
)

getKey 是唯一标识符。onUpdate 是同步处理器——当本地存储变更时触发,向你的 API 发送变更请求。由于 Collection 包装了 TanStack Query,你可以获得 stale-time、后台重新获取以及你已经拥有的所有查询管理能力。

TanStack DB 0.6(2026 年 3 月)通过浏览器的 OPFS(源私有文件系统)添加了 SQLite 支持的持久化,这意味着 Collection 在页面刷新后依然存在,无需网络往返。对于需要真正离线优先的应用,这弥合了内存响应式和持久化本地状态之间的差距。

实时查询:亚毫秒级响应式读取

实时查询从一个或多个 Collection 中读取数据。当底层数据变更时仅增量重新运行——不是重新运行完整查询,而是仅应用差量。

typescript
import { useLiveQuery, eq } from '@tanstack/react-db'

// 实时查询:进行中的订单,最新优先
const { data: openOrders } = useLiveQuery((q) =>
  q
    .from({ order: ordersCollection })
    .where(({ order }) => eq(order.status, 'open'))
    .orderBy(({ order }) => order.createdAt, 'desc')
    .select(({ order }) => ({
      id: order.id,
      total: order.total,
      customer: order.customerName,
      createdAt: order.createdAt,
    })),
)

// 当此数据变更时,全部 8 个仪表盘面板在 < 1ms 内更新:
const { data: revenue } = useLiveQuery((q) =>
  q
    .from({ order: ordersCollection })
    .where(({ order }) => eq(order.status, 'settled'))
    .select(({ order }) => ({
      total: order.total,
      currency: order.currency,
    })),
)

性能数据是明确的:据 TanStack DB 0.6 公告,在排序的 10 万项 Collection 中更新一行,在 M1 Pro 上约需 0.7ms。成本随变更大小而非数据总量增长。

支持跨 Collection 关联:

typescript
const { data: orderItems } = useLiveQuery((q) =>
  q
    .from({ order: ordersCollection, item: itemsCollection })
    .where(({ order, item }) => eq(order.id, item.orderId))
    .select(({ order, item }) => ({
      orderId: order.id,
      total: order.total,
      itemCount: item.count,
    })),
)

变更:自动回滚的乐观更新

即时感知的魔法就在这里。调用 Collection 的更新方法后,本地存储立即变更,onUpdate 处理器在后台触发:

typescript
// 乐观更新:UI 在 API 调用前更新
ordersCollection.update(orderId, (draft) => {
  draft.status = 'cancelled'
  draft.cancelledAt = Date.now()
})

如果 API 调用失败——网络错误、500、冲突——TanStack DB 自动回滚。无需手动回滚逻辑。无需 setOptimisticUpdate / revertOptimisticUpdate 的繁琐操作。

对于有依赖关系的多步骤流程,createOptimisticAction 提供事务性包装:

typescript
const { execute } = createOptimisticAction(async () => {
  await ordersCollection.update(orderId, (d) => { d.status = 'processing' })
  await paymentsCollection.insert({ orderId, amount: orderTotal })
  await inventoryCollection.update(itemId, (d) => { d.reserved = true })
})

await execute()
// 任意步骤失败:全部回滚

三种同步模式

TanStack DB 提供三种数据加载策略。模式选择决定了初始加载行为:

模式行为适用场景
Eager(默认)初始化时加载全部记录小数据集 < 5 万行
On-demand仅加载查询请求的数据大数据集,按需加载
Progressive快速首屏 + 后台完整同步需要即时 UI + 完整数据的应用

对于有 5 万订单和 5 个过滤面板的仪表盘,On-demand 模式意味着每个面板首次渲染时仅加载其过滤后的子集。Progressive 模式立即显示前 100 条结果,同时在后台同步完整数据集。

typescript
const ordersCollection = createCollection(
  queryCollectionOptions({
    queryKey: ['orders'],
    queryFn: fetchOrders,
    getKey: (o) => o.id,
    syncMode: 'progressive', // 快速首屏,后台完整同步
  }),
)

TanStack Query + TanStack DB:各司其职

TanStack DB 不是用来替换 TanStack Query 的。官方文档明确说明:"TanStack DB 需要 @tanstack/react-query。" 它们是搭档,不是对手。

心智模型是分层的:

  • TanStack Query 管理异步服务端状态:获取、缓存、后台重新获取、去重。服务端是数据源。
  • TanStack DB 管理本地响应式状态:Collection、实时查询、乐观变更。本地存储是服务端的快速镜像。

集成通过 queryCollectionOptions 实现——Collection 使用 TanStack Query 作为传输层。queryClient 选项将 Collection 连接到你现有的 TanStack Query 实例:

typescript
import { createCollection } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'

const ordersCollection = createCollection(
  queryCollectionOptions({
    queryKey: ['orders'],
    queryFn: fetchOrders,
    queryClient, // ← 连接到你的 TanStack Query 客户端
    getKey: (o) => o.id,
  }),
)

如果你已经在使用 TanStack Query,逐步迁移添加 TanStack DB 的成本是逐个 Collection 迁移。无需一次性重写全部。


TanStack DB 的不适配场景

TanStack DB 在仪表盘、Kanban 和任何需要写入后保持一致的派生数据视图的场景中表现出色。以下情况是不合适的选择:

  • 简单的获取-渲染:一个列表,无过滤器,无派生视图。单独使用 TanStack Query 就足够且更简单。
  • 真正离线优先且需要同步协议:TanStack DB 默认是内存存储(v0.6 添加了 SQLite 持久化但无内置同步协议)。对于需要 ElectricSQL 或 Replicache 式双向同步的应用,直接使用那些库。
  • 使用规范化缓存的 GraphQL:Apollo Client 和 urql 有自己的规范化缓存。TanStack DB 适用于 REST 和 tRPC 后端。

TanStack DB 与替代方案对比

特性TanStack DBRxDB单独使用 TanStack Query
本地查询引擎✅ 差分数据流✅ IndexedDB❌
兼容现有 REST API✅❌(需要适配器)✅
带回滚的乐观变更✅ 自动✅ 手动✅ 手动
亚毫秒级响应式更新✅部分支持❌
TanStack 生态集成✅ 原生❌✅
打包体积~5KB 核心库~50KB+N/A
成熟度Beta稳定稳定

RxDB 是一个以 IndexedDB 为底层支持的完整本地优先数据库。TanStack DB 是一个响应式查询层,与你现有 API 协同工作——无需新后端。对于已有 REST API 且希望仪表盘即时响应的独立开发者,TanStack DB 是更低摩擦的选择。


可观察状态的额外收益

每个 TanStack DB Collection 都暴露了一个 TanStack Store 实例。这意味着你可以在不使用 hook API 的组件中响应式读取 Collection 状态:

typescript
import { useStore } from '@tanstack/react-store'

function OrderCountBadge() {
  const count = useStore(ordersCollection.store, (state) =>
    state.items.filter(i => i.status === 'open').length,
  )

  return count > 0 ? <Badge>{count}</Badge> : null
}

store 与驱动 TanStack Store 的可观察原语是同一个。任何依赖 Collection 数据的 UI 状态都可以直接读取——无需额外的 useState 或 props 穿透。

TanStack DB 0.6 添加了虚拟 props,将同步元数据暴露为可查询字段:$synced(已由服务端确认还是仍为乐观状态)、$origin(本地变更还是来自上游同步)、$key(Collection 键)。这些支持发件箱视图——向用户显示哪些消息仍在等待中——无需任何额外状态管理。


相关阅读: Tanstack Store Vs Zustand Vs Redux 2026 · Clerk Pricing Deep Dive What 10K Mau Actually Costs Your Saas · Tanstack Ship For Agencies Delivering Client Saas Projects On A Fixed

我的生产环境检查清单

在项目中添加 TanStack DB 前:

  • 从一个 Collection 开始。 选择一个有过滤器或派生视图的列表。先迁移它。测量前后对比。
  • 选择正确的同步模式。 <5 万行用 Eager。大型表用 On-demand。需要快速首屏和完整数据的仪表盘用 Progressive。
  • 正确连接 onUpdate 处理器。 回滚仅在 onUpdate 是同步到 API 的单一入口时才有效。如果你用直接 fetch 绕过它,DB 无法感知变更。
  • 测试回滚。 触发一次变更,让 API 返回 500,确认 UI 无需任何手动代码即自动还原。
  • 不要过度 Collection 化。 一个巨型 Collection 会破坏增量更新模型。按领域模型分离 Collection,用关联查询处理跨 Collection 查询。
  • 关注打包体积。 TanStack DB 核心约 5KB。TanStack Query 额外约 15KB。TanStack Store 额外约 3KB。总计约 23KB——对仪表盘合理,对落地页过重。

TanStack DB 现已在 TanStack Ship 中作为可选数据层插件提供。 查看它如何融入完整的 TanStack 生态对比和逐特性详细拆解。