tanstack-db
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOverview
概述
TanStack DB is a client-side embedded database layer built on differential dataflow. It maintains normalized collections, uses incremental computation for live queries, provides automatic optimistic mutations, and integrates with TanStack Query for data fetching. Sub-millisecond updates even with 100k+ rows.
Package:
Query Integration:
Status: Beta (v0.5)
@tanstack/react-db@tanstack/query-db-collectionTanStack DB 是基于差分数据流构建的客户端嵌入式数据库层。它维护规范化的集合,使用增量计算实现实时查询,提供自动乐观更新,并与TanStack Query集成以实现数据获取。即使处理10万+行数据,更新也能达到亚毫秒级。
包:
Query集成:
状态: Beta版本(v0.5)
@tanstack/react-db@tanstack/query-db-collectionInstallation
安装
bash
npm install @tanstack/react-db @tanstack/query-db-collectionbash
npm install @tanstack/react-db @tanstack/query-db-collectionCore Concepts
核心概念
- Collections: Normalized data stores wrapping data sources (TanStack Query, Electric, etc.)
- Live Queries: Reactive subscriptions with SQL-like query builder
- Optimistic Mutations: Automatic instant UI updates with rollback on failure
- Differential Dataflow: Only recomputes affected query results on changes
- 集合:封装数据源(TanStack Query、Electric等)的规范化数据存储
- 实时查询:带有类SQL查询构建器的响应式订阅
- 乐观更新:自动即时更新UI,失败时自动回滚
- 差分数据流:仅在数据变化时重新计算受影响的查询结果
Collections
集合
Creating a Collection
创建集合
typescript
import { createCollection } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ['todos'],
queryFn: async () => api.todos.getAll(),
getKey: (item) => item.id,
schema: todoSchema,
onInsert: async ({ transaction }) => {
await Promise.all(
transaction.mutations.map((mutation) =>
api.todos.create(mutation.modified)
)
)
},
onUpdate: async ({ transaction }) => {
await Promise.all(
transaction.mutations.map((mutation) =>
api.todos.update(mutation.modified)
)
)
},
onDelete: async ({ transaction }) => {
await Promise.all(
transaction.mutations.map((mutation) =>
api.todos.delete(mutation.original.id)
)
)
},
})
)typescript
import { createCollection } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ['todos'],
queryFn: async () => api.todos.getAll(),
getKey: (item) => item.id,
schema: todoSchema,
onInsert: async ({ transaction }) => {
await Promise.all(
transaction.mutations.map((mutation) =>
api.todos.create(mutation.modified)
)
)
},
onUpdate: async ({ transaction }) => {
await Promise.all(
transaction.mutations.map((mutation) =>
api.todos.update(mutation.modified)
)
)
},
onDelete: async ({ transaction }) => {
await Promise.all(
transaction.mutations.map((mutation) =>
api.todos.delete(mutation.original.id)
)
)
},
})
)Sync Modes
同步模式
typescript
// Eager (default): Load entire collection upfront. Best for <10k rows.
const smallCollection = createCollection(
queryCollectionOptions({ syncMode: 'eager', /* ... */ })
)
// On-Demand: Load only what queries request. Best for >50k rows, search.
const largeCollection = createCollection(
queryCollectionOptions({
syncMode: 'on-demand',
queryFn: async (ctx) => {
const params = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)
return api.getProducts(params)
},
})
)
// Progressive: Load query subset immediately, full sync in background.
const collaborativeCollection = createCollection(
queryCollectionOptions({ syncMode: 'progressive', /* ... */ })
)typescript
// 立即同步(默认):预先加载整个集合。适用于少于1万行的数据。
const smallCollection = createCollection(
queryCollectionOptions({ syncMode: 'eager', /* ... */ })
)
// 按需同步:仅加载查询请求的数据。适用于超过5万行的数据或搜索场景。
const largeCollection = createCollection(
queryCollectionOptions({
syncMode: 'on-demand',
queryFn: async (ctx) => {
const params = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)
return api.getProducts(params)
},
})
)
// 渐进式同步:立即加载查询子集,后台进行全量同步。适用于协作场景。
const collaborativeCollection = createCollection(
queryCollectionOptions({ syncMode: 'progressive', /* ... */ })
)Live Queries
实时查询
Basic Query
基础查询
typescript
import { useLiveQuery } from '@tanstack/react-db'
import { eq } from '@tanstack/db'
function TodoList() {
const { data: todos } = useLiveQuery((query) =>
query
.from({ todos: todoCollection })
.where(({ todos }) => eq(todos.completed, false))
)
return <ul>{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>
}typescript
import { useLiveQuery } from '@tanstack/react-db'
import { eq } from '@tanstack/db'
function TodoList() {
const { data: todos } = useLiveQuery((query) =>
query
.from({ todos: todoCollection })
.where(({ todos }) => eq(todos.completed, false))
)
return <ul>{todos.map(todo => <li key={todo.id}>{todo.text}</li>)}</ul>
}Query Builder API
查询构建器API
typescript
const { data } = useLiveQuery((q) =>
q
.from({ t: todoCollection })
.where(({ t }) => eq(t.status, 'active'))
.orderBy(({ t }) => t.createdAt, 'desc')
.limit(10)
)typescript
const { data } = useLiveQuery((q) =>
q
.from({ t: todoCollection })
.where(({ t }) => eq(t.status, 'active'))
.orderBy(({ t }) => t.createdAt, 'desc')
.limit(10)
)Joins
关联查询
typescript
const { data } = useLiveQuery((q) =>
q
.from({ t: todoCollection })
.innerJoin(
{ u: userCollection },
({ t, u }) => eq(t.userId, u.id)
)
.innerJoin(
{ p: projectCollection },
({ u, p }) => eq(u.projectId, p.id)
)
.where(({ p }) => eq(p.id, currentProject.id))
)typescript
const { data } = useLiveQuery((q) =>
q
.from({ t: todoCollection })
.innerJoin(
{ u: userCollection },
({ t, u }) => eq(t.userId, u.id)
)
.innerJoin(
{ p: projectCollection },
({ u, p }) => eq(u.projectId, p.id)
)
.where(({ p }) => eq(p.id, currentProject.id))
)Filter Operators
过滤操作符
typescript
import { eq, lt, and } from '@tanstack/db'
// Equality
eq(field, value)
// Less than
lt(field, value)
// AND
and(eq(product.category, 'electronics'), lt(product.price, 100))typescript
import { eq, lt, and } from '@tanstack/db'
// 等于
eq(field, value)
// 小于
lt(field, value)
// 逻辑与
and(eq(product.category, 'electronics'), lt(product.price, 100))With Ordering and Limits
排序与限制
typescript
const { data } = useLiveQuery((q) =>
q
.from({ product: productsCollection })
.where(({ product }) =>
and(eq(product.category, 'electronics'), lt(product.price, 100))
)
.orderBy(({ product }) => product.price, 'asc')
.limit(10)
)typescript
const { data } = useLiveQuery((q) =>
q
.from({ product: productsCollection })
.where(({ product }) =>
and(eq(product.category, 'electronics'), lt(product.price, 100))
)
.orderBy(({ product }) => product.price, 'asc')
.limit(10)
)Optimistic Mutations
乐观更新
Insert
插入
typescript
todoCollection.insert({
id: uuid(),
text: 'New todo',
completed: false,
})
// Immediately: updates all live queries referencing this collection
// Background: calls onInsert handler to sync with server
// On failure: automatic rollbacktypescript
todoCollection.insert({
id: uuid(),
text: 'New todo',
completed: false,
})
// 立即:更新所有引用该集合的实时查询
// 后台:调用onInsert处理程序与服务器同步
// 失败时:自动回滚No Manual Boilerplate
无需手动编写样板代码
| Before (TanStack Query only) | After (TanStack DB) |
|---|---|
Manual | Automatic |
Manual | Automatic |
| Per-mutation cache invalidation | All live queries update automatically |
| 之前(仅使用TanStack Query) | 之后(使用TanStack DB) |
|---|---|
手动编写 | 自动处理 |
手动编写 | 自动处理 |
| 每次更新都要手动失效缓存 | 所有实时查询自动更新 |
Query-Driven Sync (On-Demand)
查询驱动同步(按需模式)
Live queries automatically generate optimized network requests:
typescript
// This live query...
useLiveQuery((q) =>
q.from({ product: productsCollection })
.where(({ product }) => and(eq(product.category, 'electronics'), lt(product.price, 100)))
.orderBy(({ product }) => product.price, 'asc')
.limit(10)
)
// ...automatically generates:
// GET /api/products?category=electronics&price_lt=100&sort=price:asc&limit=10实时查询会自动生成优化的网络请求:
typescript
// 这个实时查询...
useLiveQuery((q) =>
q.from({ product: productsCollection })
.where(({ product }) => and(eq(product.category, 'electronics'), lt(product.price, 100)))
.orderBy(({ product }) => product.price, 'asc')
.limit(10)
)
// ...会自动生成:
// GET /api/products?category=electronics&price_lt=100&sort=price:asc&limit=10Predicate Mapping
断言映射
typescript
queryFn: async (ctx) => {
const { filters, sorts, limit } = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)
const params = new URLSearchParams()
filters.forEach(({ field, operator, value }) => {
if (operator === 'eq') params.set(field.join('.'), String(value))
else if (operator === 'lt') params.set(`${field.join('.')}_lt`, String(value))
})
if (limit) params.set('limit', String(limit))
return fetch(`/api/products?${params}`).then(r => r.json())
}typescript
queryFn: async (ctx) => {
const { filters, sorts, limit } = parseLoadSubsetOptions(ctx.meta?.loadSubsetOptions)
const params = new URLSearchParams()
filters.forEach(({ field, operator, value }) => {
if (operator === 'eq') params.set(field.join('.'), String(value))
else if (operator === 'lt') params.set(`${field.join('.')}_lt`, String(value))
})
if (limit) params.set('limit', String(limit))
return fetch(`/api/products?${params}`).then(r => r.json())
}Performance
性能
| Operation | Latency |
|---|---|
| Single row update (100k sorted collection) | ~0.7 ms |
| Subsequent queries (after sync) | <1 ms |
| Join across collections | Sub-millisecond |
| 操作 | 延迟 |
|---|---|
| 单行更新(10万行排序集合) | ~0.7毫秒 |
| 后续查询(同步后) | <1毫秒 |
| 跨集合关联查询 | 亚毫秒级 |
Supported Collection Types
支持的集合类型
- Query Collection - TanStack Query integration
- Electric Collection - Electric SQL real-time sync
- TrailBase Collection - TrailBase backend
- RxDB Collection - RxDB integration
- PowerSync Collection - PowerSync sync
- LocalStorage Collection - Browser persistence
- LocalOnly Collection - In-memory only
- Query Collection - TanStack Query集成
- Electric Collection - Electric SQL实时同步
- TrailBase Collection - TrailBase后端集成
- RxDB Collection - RxDB集成
- PowerSync Collection - PowerSync同步
- LocalStorage Collection - 浏览器持久化
- LocalOnly Collection - 仅内存存储
API Summary
API摘要
typescript
import { createCollection, useLiveQuery } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'
import { eq, lt, and, parseLoadSubsetOptions } from '@tanstack/db'typescript
import { createCollection, useLiveQuery } from '@tanstack/react-db'
import { queryCollectionOptions } from '@tanstack/query-db-collection'
import { eq, lt, and, parseLoadSubsetOptions } from '@tanstack/db'Best Practices
最佳实践
- Define collections at module level - they're singletons
- Choose the right sync mode: (<10k),
eager(>50k),on-demand(collaborative)progressive - Use joins instead of view-specific APIs - load normalized collections once
- Let TanStack Query handle fetching - DB augments Query, doesn't replace it
- Use to map live query predicates to API params
parseLoadSubsetOptions - Rely on automatic optimistic updates - don't manually manage optimistic state
- Use schemas for runtime validation and TypeScript inference
- Leverage incremental computation - let the engine handle filtering vs manual
.filter()
- 在模块级别定义集合 - 它们是单例
- 选择合适的同步模式:(少于1万行)、
eager(超过5万行)、on-demand(协作场景)progressive - 使用关联查询而非视图特定API - 只需加载一次规范化集合
- 让TanStack Query处理数据获取 - DB是对Query的增强,而非替代
- 使用将实时查询断言映射为API参数
parseLoadSubsetOptions - 依赖自动乐观更新 - 不要手动管理乐观状态
- 使用Schema 进行运行时验证和TypeScript类型推断
- 利用增量计算 - 让引擎处理过滤,而非手动使用
.filter()
Common Pitfalls
常见陷阱
- Creating collections inside components (should be module-level)
- Trying to replace TanStack Query entirely (DB builds on top of it)
- Using manual in render instead of live query
.filter()clauseswhere - Not providing for proper normalization
getKey - Forgetting mutation handlers (,
onInsert,onUpdate) for server synconDelete
- 在组件内部创建集合(应在模块级别创建)
- 试图完全替代TanStack Query(DB构建在Query之上)
- 在渲染中使用手动而非实时查询的
.filter()子句where - 未提供以实现正确的规范化
getKey - 忘记编写更新处理程序(、
onInsert、onUpdate)以实现与服务器同步onDelete