QueryCollection
ElectricCollection
TrailBaseCollection
LocalStorageCollection
LocalOnlyCollection
useLiveQuery
hookqueryBuilder
mutationFn
createOptimisticAction
insert
update
delete
optimistic: false
欢迎来到 TanStack DB 文档。
TanStack DB 是一个反应式客户端存储,用于构建超快的同步应用。它通过集合、实时查询和乐观更新来扩展 TanStack Query。
TanStack DB 的工作原理是
// Define collections to load data into
const todoCollection = createCollection({
// ...your config
onUpdate: updateMutationFn,
})
const Todos = () => {
// Bind data using live queries
const { data: todos } = useLiveQuery((q) =>
q.from({ todo: todoCollection }).where(({ todo }) => todo.completed)
)
const complete = (todo) => {
// Instantly applies optimistic state
todoCollection.update(todo.id, (draft) => {
draft.completed = true
})
}
return (
<ul>
{todos.map((todo) => (
<li key={todo.id} onClick={() => complete(todo)}>
{todo.text}
</li>
))}
</ul>
)
}
// Define collections to load data into
const todoCollection = createCollection({
// ...your config
onUpdate: updateMutationFn,
})
const Todos = () => {
// Bind data using live queries
const { data: todos } = useLiveQuery((q) =>
q.from({ todo: todoCollection }).where(({ todo }) => todo.completed)
)
const complete = (todo) => {
// Instantly applies optimistic state
todoCollection.update(todo.id, (draft) => {
draft.completed = true
})
}
return (
<ul>
{todos.map((todo) => (
<li key={todo.id} onClick={() => complete(todo)}>
{todo.text}
</li>
))}
</ul>
)
}
集合是可填充数据的类型化对象集。它们旨在将数据加载到您的应用中与数据绑定到您的组件解耦。
集合可以通过多种方式填充,包括
一旦将数据放入集合中,您就可以在组件中使用实时查询跨集合进行查询。
实时查询用于从集合中查询数据。实时查询是反应式的:当底层数据发生变化导致查询结果受到影响时,结果会增量更新并从查询中返回,从而触发重新渲染。
TanStack DB 实时查询是使用 d2ts 实现的,这是一个 differential dataflow 的 Typescript 实现。这使得查询结果能够增量更新(而不是重新运行整个查询)。这使得它们速度极快,通常在毫秒以内,即使是高度复杂的查询。
实时查询支持跨集合的连接。这允许您
每个查询都会返回另一个集合,而该集合本身也可以被查询。
有关实时查询的更多详细信息,请参阅 实时查询 文档。
集合支持 insert、update 和 delete 操作。当调用这些操作时,默认会触发相应的 onInsert、onUpdate、onDelete 处理程序,这些处理程序负责将更改写入后端。
// Define collection with persistence handlers
const todoCollection = createCollection({
id: "todos",
// ... other config
onUpdate: async ({ transaction }) => {
const { original, changes } = transaction.mutations[0]
await api.todos.update(original.id, changes)
},
})
// Immediately applies optimistic state
todoCollection.update(todo.id, (draft) => {
draft.completed = true
})
// Define collection with persistence handlers
const todoCollection = createCollection({
id: "todos",
// ... other config
onUpdate: async ({ transaction }) => {
const { original, changes } = transaction.mutations[0]
await api.todos.update(original.id, changes)
},
})
// Immediately applies optimistic state
todoCollection.update(todo.id, (draft) => {
draft.completed = true
})
集合不是直接修改集合数据,而是内部将同步/加载的数据视为不可变的,并维护一个本地更改的集合作为乐观状态。当实时查询从集合读取数据时,它们会看到一个本地视图,该视图将本地乐观更改叠加在不可变同步数据之上。
乐观状态将一直保留,直到 onUpdate(在这种情况下)处理程序解析——此时数据将被持久化到服务器并同步回本地集合。
如果处理程序抛出错误,乐观状态将被回滚。
更改基于 Transaction 基元。
对于简单的状态更改,直接修改集合并通过操作符处理程序持久化就足够了。
但是对于更复杂的用例,您可以使用 createOptimisticAction 直接创建自定义操作,或使用 createTransaction 创建自定义事务。这允许您执行诸如跨多个集合执行多个更改的事务、执行带中间回滚的链式事务等操作。
例如,在下面的代码中,mutationFn 首先使用 await api.todos.update(updatedTodo) 将写入发送到服务器,然后调用 await collection.refetch() 来触发使用 TanStack Query 重新获取集合内容。当第二个 await 解析时,集合与最新更改同步,乐观状态安全地被丢弃。
const updateTodo = createOptimisticAction<{ id: string }>({
onMutate,
mutationFn: async ({ transaction }) => {
const { collection, modified: updatedTodo } = transaction.mutations[0]
await api.todos.update(updatedTodo)
await collection.refetch()
},
})
const updateTodo = createOptimisticAction<{ id: string }>({
onMutate,
mutationFn: async ({ transaction }) => {
const { collection, modified: updatedTodo } = transaction.mutations[0]
await api.todos.update(updatedTodo)
await collection.refetch()
},
})
这结合起来支持一种单向数据流模型,将 redux/flux 风格的状态管理模式从客户端扩展到服务器
通过即时的内部乐观状态循环,以及稍后发生的更慢的外部持久化到服务器和将更新后的服务器状态同步回集合的循环。
有许多内置的集合类型
您还可以使用
所有集合都可选地(尽管强烈推荐)支持添加 schema。
如果提供,则必须是 Standard Schema 兼容的模式实例,例如 Zod 或 Effect 模式。
集合将使用该模式对乐观更新进行客户端验证。
集合将使用该模式作为其类型,因此如果您提供了模式,则不能再传递显式类型(例如 createCollection<Todo>())。
TanStack Query 使用托管查询来获取数据。使用 queryCollectionOptions 来使用 TanStack Query 将数据获取到集合中
import { createCollection } from "@tanstack/react-db"
import { queryCollectionOptions } from "@tanstack/query-db-collection"
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ["todoItems"],
queryFn: async () => {
const response = await fetch("/api/todos");
return response.json();
},
getKey: (item) => item.id,
schema: todoSchema, // any standard schema
})
)
import { createCollection } from "@tanstack/react-db"
import { queryCollectionOptions } from "@tanstack/query-db-collection"
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ["todoItems"],
queryFn: async () => {
const response = await fetch("/api/todos");
return response.json();
},
getKey: (item) => item.id,
schema: todoSchema, // any standard schema
})
)
集合将用查询结果填充。
Electric 是一个用于 Postgres 的读取路径同步引擎。它允许您将 Postgres 数据库的子集数据同步,通过您的 API,到 TanStack DB 集合中。
Electric 同步的主要基元是 Shape。使用 electricCollectionOptions 将 shape 同步到集合中
import { createCollection } from "@tanstack/react-db"
import { electricCollectionOptions } from "@tanstack/electric-db-collection"
export const todoCollection = createCollection(
electricCollectionOptions({
id: "todos",
shapeOptions: {
url: "https://example.com/v1/shape",
params: {
table: "todos",
},
},
getKey: (item) => item.id,
schema: todoSchema,
})
)
import { createCollection } from "@tanstack/react-db"
import { electricCollectionOptions } from "@tanstack/electric-db-collection"
export const todoCollection = createCollection(
electricCollectionOptions({
id: "todos",
shapeOptions: {
url: "https://example.com/v1/shape",
params: {
table: "todos",
},
},
getKey: (item) => item.id,
schema: todoSchema,
})
)
Electric 集合需要两个特定于 Electric 的选项
在调用 collection.preload() 或查询它之前,新集合不会开始同步。
Electric shapes 允许您使用 where 子句过滤数据
export const myPendingTodos = createCollection(
electricCollectionOptions({
id: "todos",
shapeOptions: {
url: "https://example.com/v1/shape",
params: {
table: "todos",
where: `
status = 'pending'
AND
user_id = '${user.id}'
`,
},
},
getKey: (item) => item.id,
schema: todoSchema,
})
)
export const myPendingTodos = createCollection(
electricCollectionOptions({
id: "todos",
shapeOptions: {
url: "https://example.com/v1/shape",
params: {
table: "todos",
where: `
status = 'pending'
AND
user_id = '${user.id}'
`,
},
},
getKey: (item) => item.id,
schema: todoSchema,
})
)
提示
Shape where 子句(用于过滤同步到 ElectricCollection 的数据)与您在组件中用于查询数据的 实时查询 不同。
实时查询比 shapes 更具表现力,允许您跨集合查询、连接、聚合等。Shapes 仅包含过滤后的数据库表,用于填充集合中的数据。
如果您需要更多控制同步到集合的数据,Electric 允许您 使用您的 API 作为代理来授权和过滤数据。
有关更多信息,请参阅 Electric 文档。
TrailBase 是一个易于自托管的、单一可执行的应用程序后端,内置 SQLite、V8 JS 运行时、身份验证、管理 UI 和同步功能。
TrailBase 允许您通过 Record APIs 公开表,并在设置 enable_subscriptions 时订阅更改。使用 trailBaseCollectionOptions 将记录同步到集合中
import { createCollection } from "@tanstack/react-db"
import { trailBaseCollectionOptions } from "@tanstack/trailbase-db-collection"
import { initClient } from "trailbase"
const trailBaseClient = initClient(`https://trailbase.io`)
export const todoCollection = createCollection<SelectTodo, Todo>(
electricCollectionOptions({
id: "todos",
recordApi: trailBaseClient.records(`todos`),
getKey: (item) => item.id,
schema: todoSchema,
parse: {
created_at: (ts) => new Date(ts * 1000),
},
serialize: {
created_at: (date) => Math.floor(date.valueOf() / 1000),
},
})
)
import { createCollection } from "@tanstack/react-db"
import { trailBaseCollectionOptions } from "@tanstack/trailbase-db-collection"
import { initClient } from "trailbase"
const trailBaseClient = initClient(`https://trailbase.io`)
export const todoCollection = createCollection<SelectTodo, Todo>(
electricCollectionOptions({
id: "todos",
recordApi: trailBaseClient.records(`todos`),
getKey: (item) => item.id,
schema: todoSchema,
parse: {
created_at: (ts) => new Date(ts * 1000),
},
serialize: {
created_at: (date) => Math.floor(date.valueOf() / 1000),
},
})
)
此集合需要以下特定于 TrailBase 的选项
在调用 collection.preload() 或查询它之前,新集合不会开始同步。
localStorage 集合存储少量仅本地数据,这些数据可在浏览器会话之间持久化,并在浏览器标签页之间实时同步。所有数据都存储在一个 localStorage 键下,并使用 storage 事件自动同步。
使用 localStorageCollectionOptions 创建将数据存储在 localStorage 中的集合
import { createCollection } from "@tanstack/react-db"
import { localStorageCollectionOptions } from "@tanstack/react-db"
export const userPreferencesCollection = createCollection(
localStorageCollectionOptions({
id: "user-preferences",
storageKey: "app-user-prefs", // localStorage key
getKey: (item) => item.id,
schema: userPrefsSchema,
})
)
import { createCollection } from "@tanstack/react-db"
import { localStorageCollectionOptions } from "@tanstack/react-db"
export const userPreferencesCollection = createCollection(
localStorageCollectionOptions({
id: "user-preferences",
storageKey: "app-user-prefs", // localStorage key
getKey: (item) => item.id,
schema: userPrefsSchema,
})
)
localStorage 集合需要
Mutation handlers(onInsert、onUpdate、onDelete)完全可选。无论是否提供处理程序,数据都会持久化到 localStorage。您可以提供替代的存储后端,如 sessionStorage 或匹配 localStorage API 的自定义实现。
export const sessionCollection = createCollection(
localStorageCollectionOptions({
id: "session-data",
storageKey: "session-key",
storage: sessionStorage, // Use sessionStorage instead
getKey: (item) => item.id,
})
)
export const sessionCollection = createCollection(
localStorageCollectionOptions({
id: "session-data",
storageKey: "session-key",
storage: sessionStorage, // Use sessionStorage instead
getKey: (item) => item.id,
})
)
提示
localStorage 集合非常适合用户偏好、UI 状态以及其他需要本地持久化但不需要服务器同步的数据。对于服务器同步的数据,请改用 QueryCollection 或 ElectricCollection。
LocalOnly 集合专为内存中的客户端数据或 UI 状态而设计,这些数据不需要跨浏览器会话持久化或在标签页之间同步。它们提供了一种简单的方法来管理临时、仅会话的数据,并支持完整的乐观更新。
使用 localOnlyCollectionOptions 创建将数据仅存储在内存中的集合
import { createCollection } from "@tanstack/react-db"
import { localOnlyCollectionOptions } from "@tanstack/react-db"
export const uiStateCollection = createCollection(
localOnlyCollectionOptions({
id: "ui-state",
getKey: (item) => item.id,
schema: uiStateSchema,
// Optional initial data to populate the collection
initialData: [
{ id: "sidebar", isOpen: false },
{ id: "theme", mode: "light" },
],
})
)
import { createCollection } from "@tanstack/react-db"
import { localOnlyCollectionOptions } from "@tanstack/react-db"
export const uiStateCollection = createCollection(
localOnlyCollectionOptions({
id: "ui-state",
getKey: (item) => item.id,
schema: uiStateSchema,
// Optional initial data to populate the collection
initialData: [
{ id: "sidebar", isOpen: false },
{ id: "theme", mode: "light" },
],
})
)
LocalOnly 集合需要
可选配置
更新处理程序完全可选。提供时,它们会在乐观状态确认之前调用。集合会在内部自动管理从乐观状态到已确认状态的过渡。
export const tempDataCollection = createCollection(
localOnlyCollectionOptions({
id: "temp-data",
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
// Custom logic before confirming the insert
console.log("Inserting:", transaction.mutations[0].modified)
},
onUpdate: async ({ transaction }) => {
// Custom logic before confirming the update
const { original, modified } = transaction.mutations[0]
console.log("Updating from", original, "to", modified)
},
})
)
export const tempDataCollection = createCollection(
localOnlyCollectionOptions({
id: "temp-data",
getKey: (item) => item.id,
onInsert: async ({ transaction }) => {
// Custom logic before confirming the insert
console.log("Inserting:", transaction.mutations[0].modified)
},
onUpdate: async ({ transaction }) => {
// Custom logic before confirming the update
const { original, modified } = transaction.mutations[0]
console.log("Updating from", original, "to", modified)
},
})
)
提示
LocalOnly 集合非常适合临时 UI 状态、表单数据或任何不需要持久化的客户端数据。对于需要在会话之间持久化的数据,请改用 LocalStorageCollection。
实时查询返回集合。这允许您从其他集合派生集合。
例如
import { createLiveQueryCollection, eq } from "@tanstack/db"
// Imagine you have a collection of todos.
const todoCollection = createCollection({
// config
})
// You can derive a new collection that's a subset of it.
const completedTodoCollection = createLiveQueryCollection({
startSync: true,
query: (q) =>
q.from({ todo: todoCollection }).where(({ todo }) => todo.completed),
})
import { createLiveQueryCollection, eq } from "@tanstack/db"
// Imagine you have a collection of todos.
const todoCollection = createCollection({
// config
})
// You can derive a new collection that's a subset of it.
const completedTodoCollection = createLiveQueryCollection({
startSync: true,
query: (q) =>
q.from({ todo: todoCollection }).where(({ todo }) => todo.completed),
})
这也可与连接一起使用,从多个源集合派生集合。并且它是递归的——您可以从其他派生集合派生集合。更改使用差分数据流高效传播,并且它们一直都是集合。
在 ../packages/db/src/collection.ts 中有一个 Collection 接口。您可以使用此接口来实现自己的集合类型。
请参考现有实现,例如 ../packages/db、../packages/query-db-collection、../packages/electric-db-collection 和 ../packages/trailbase-db-collection。
使用 useLiveQuery hook 将实时查询结果分配给 React 组件中的状态变量
import { useLiveQuery } from '@tanstack/react-db'
import { eq } from '@tanstack/db'
const Todos = () => {
const { data: todos } = useLiveQuery((q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.completed, false))
.orderBy(({ todo }) => todo.created_at, 'asc')
.select(({ todo }) => ({
id: todo.id,
text: todo.text
}))
)
return <List items={ todos } />
}
import { useLiveQuery } from '@tanstack/react-db'
import { eq } from '@tanstack/db'
const Todos = () => {
const { data: todos } = useLiveQuery((q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.completed, false))
.orderBy(({ todo }) => todo.created_at, 'asc')
.select(({ todo }) => ({
id: todo.id,
text: todo.text
}))
)
return <List items={ todos } />
}
您还可以跨集合进行连接查询
import { useLiveQuery } from '@tanstack/react-db'
import { eq } from '@tanstack/db'
const Todos = () => {
const { data: todos } = useLiveQuery((q) =>
q
.from({ todos: todoCollection })
.join(
{ lists: listCollection },
({ todos, lists }) => eq(lists.id, todos.listId),
'inner'
)
.where(({ lists }) => eq(lists.active, true))
.select(({ todos, lists }) => ({
id: todos.id,
title: todos.title,
listName: lists.name
}))
)
return <List items={ todos } />
}
import { useLiveQuery } from '@tanstack/react-db'
import { eq } from '@tanstack/db'
const Todos = () => {
const { data: todos } = useLiveQuery((q) =>
q
.from({ todos: todoCollection })
.join(
{ lists: listCollection },
({ todos, lists }) => eq(lists.id, todos.listId),
'inner'
)
.where(({ lists }) => eq(lists.active, true))
.select(({ todos, lists }) => ({
id: todos.id,
title: todos.title,
listName: lists.name
}))
)
return <List items={ todos } />
}
您还可以直接构建查询(在组件生命周期之外)使用底层的 queryBuilder API
import { createLiveQueryCollection, eq } from "@tanstack/db"
const completedTodos = createLiveQueryCollection({
startSync: true,
query: (q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.completed, true)),
})
const results = completedTodos.toArray
import { createLiveQueryCollection, eq } from "@tanstack/db"
const completedTodos = createLiveQueryCollection({
startSync: true,
query: (q) =>
q
.from({ todo: todoCollection })
.where(({ todo }) => eq(todo.completed, true)),
})
const results = completedTodos.toArray
还请注意
有关更多详细信息,请参阅 实时查询 文档。
事务性更新器允许您使用以下方式批处理和暂存跨集合的本地更改
更新器使用 mutationFn 创建。您可以为整个应用定义一个单一的、通用的 mutationFn。或者您可以定义特定于集合或特定于更新的函数。
mutationFn 负责处理本地更改并对其进行处理,通常是将其发送到服务器或数据库进行存储。
重要:在您的 mutationFn 内部,您必须确保您的服务器写入已同步回来,因为当您从 mutation 函数返回时,乐观状态会被丢弃。您通常会使用特定于集合的辅助函数来完成此操作,例如 Query 的 utils.refetch()、直接写入 API,或 Electric 的 utils.awaitTxId()。
例如
import type { MutationFn } from "@tanstack/react-db"
const mutationFn: MutationFn = async ({ transaction }) => {
const response = await api.todos.create(transaction.mutations)
if (!response.ok) {
// Throwing an error will rollback the optimistic state.
throw new Error(`HTTP Error: ${response.status}`)
}
const result = await response.json()
// Wait for the transaction to be synced back from the server
// before discarding the optimistic state.
const collection: Collection = transaction.mutations[0].collection
await collection.refetch()
}
import type { MutationFn } from "@tanstack/react-db"
const mutationFn: MutationFn = async ({ transaction }) => {
const response = await api.todos.create(transaction.mutations)
if (!response.ok) {
// Throwing an error will rollback the optimistic state.
throw new Error(`HTTP Error: ${response.status}`)
}
const result = await response.json()
// Wait for the transaction to be synced back from the server
// before discarding the optimistic state.
const collection: Collection = transaction.mutations[0].collection
await collection.refetch()
}
使用 createOptimisticAction 结合您的 mutationFn 和 onMutate 函数,创建可在组件中以完全自定义方式修改数据的操作
import { createOptimisticAction } from "@tanstack/react-db"
// Create the `addTodo` action, passing in your `mutationFn` and `onMutate`.
const addTodo = createOptimisticAction<string>({
onMutate: (text) => {
// Instantly applies the local optimistic state.
todoCollection.insert({
id: uuid(),
text,
completed: false,
})
},
mutationFn: async (text, params) => {
// Persist the todo to your backend
const response = await fetch("/api/todos", {
method: "POST",
body: JSON.stringify({ text, completed: false }),
})
const result = await response.json()
// IMPORTANT: Ensure server writes have synced back before returning
// This ensures the optimistic state can be safely discarded
await todoCollection.utils.refetch()
return result
},
})
const Todo = () => {
const handleClick = () => {
// Triggers the onMutate and then the mutationFn
addTodo("🔥 Make app faster")
}
return <Button onClick={handleClick} />
}
import { createOptimisticAction } from "@tanstack/react-db"
// Create the `addTodo` action, passing in your `mutationFn` and `onMutate`.
const addTodo = createOptimisticAction<string>({
onMutate: (text) => {
// Instantly applies the local optimistic state.
todoCollection.insert({
id: uuid(),
text,
completed: false,
})
},
mutationFn: async (text, params) => {
// Persist the todo to your backend
const response = await fetch("/api/todos", {
method: "POST",
body: JSON.stringify({ text, completed: false }),
})
const result = await response.json()
// IMPORTANT: Ensure server writes have synced back before returning
// This ensures the optimistic state can be safely discarded
await todoCollection.utils.refetch()
return result
},
})
const Todo = () => {
const handleClick = () => {
// Triggers the onMutate and then the mutationFn
addTodo("🔥 Make app faster")
}
return <Button onClick={handleClick} />
}
createOptimisticAction 是一个大约 25 行的函数,它实现了一个通用的事务模式。您可以自由发明自己的模式!
以下是使用事务的一种方法。
import { createTransaction } from "@tanstack/react-db"
const addTodoTx = createTransaction({
autoCommit: false,
mutationFn: async ({ transaction }) => {
// Persist data to backend
await Promise.all(transaction.mutations.map(mutation => {
return await api.saveTodo(mutation.modified)
})
},
})
// Apply first change
addTodoTx.mutate(() => todoCollection.insert({ id: '1', text: 'First todo', completed: false }))
// user reviews change
// Apply another change
addTodoTx.mutate(() => todoCollection.insert({ id: '2', text: 'Second todo', completed: false }))
// User decides to save and we call .commit() and the mutations are persisted to the backend.
addTodoTx.commit()
import { createTransaction } from "@tanstack/react-db"
const addTodoTx = createTransaction({
autoCommit: false,
mutationFn: async ({ transaction }) => {
// Persist data to backend
await Promise.all(transaction.mutations.map(mutation => {
return await api.saveTodo(mutation.modified)
})
},
})
// Apply first change
addTodoTx.mutate(() => todoCollection.insert({ id: '1', text: 'First todo', completed: false }))
// user reviews change
// Apply another change
addTodoTx.mutate(() => todoCollection.insert({ id: '2', text: 'Second todo', completed: false }))
// User decides to save and we call .commit() and the mutations are persisted to the backend.
addTodoTx.commit()
事务经历以下状态
集合支持 insert、update 和 delete 操作。
// Insert a single item
myCollection.insert({ text: "Buy groceries", completed: false })
// Insert multiple items
insert([
{ text: "Buy groceries", completed: false },
{ text: "Walk dog", completed: false },
])
// Insert with optimistic updates disabled
myCollection.insert(
{ text: "Server-validated item", completed: false },
{ optimistic: false }
)
// Insert with metadata and optimistic control
myCollection.insert(
{ text: "Custom item", completed: false },
{
metadata: { source: "import" },
optimistic: true, // default behavior
}
)
// Insert a single item
myCollection.insert({ text: "Buy groceries", completed: false })
// Insert multiple items
insert([
{ text: "Buy groceries", completed: false },
{ text: "Walk dog", completed: false },
])
// Insert with optimistic updates disabled
myCollection.insert(
{ text: "Server-validated item", completed: false },
{ optimistic: false }
)
// Insert with metadata and optimistic control
myCollection.insert(
{ text: "Custom item", completed: false },
{
metadata: { source: "import" },
optimistic: true, // default behavior
}
)
我们使用代理来捕获更新,作为不可变的草稿乐观更新。
// Update a single item
update(todo.id, (draft) => {
draft.completed = true
})
// Update multiple items
update([todo1.id, todo2.id], (drafts) => {
drafts.forEach((draft) => {
draft.completed = true
})
})
// Update with metadata
update(todo.id, { metadata: { reason: "user update" } }, (draft) => {
draft.text = "Updated text"
})
// Update without optimistic updates
update(todo.id, { optimistic: false }, (draft) => {
draft.status = "server-validated"
})
// Update with both metadata and optimistic control
update(
todo.id,
{
metadata: { reason: "admin update" },
optimistic: false,
},
(draft) => {
draft.priority = "high"
}
)
// Update a single item
update(todo.id, (draft) => {
draft.completed = true
})
// Update multiple items
update([todo1.id, todo2.id], (drafts) => {
drafts.forEach((draft) => {
draft.completed = true
})
})
// Update with metadata
update(todo.id, { metadata: { reason: "user update" } }, (draft) => {
draft.text = "Updated text"
})
// Update without optimistic updates
update(todo.id, { optimistic: false }, (draft) => {
draft.status = "server-validated"
})
// Update with both metadata and optimistic control
update(
todo.id,
{
metadata: { reason: "admin update" },
optimistic: false,
},
(draft) => {
draft.priority = "high"
}
)
// Delete a single item
delete todo.id
// Delete multiple items
delete [todo1.id, todo2.id]
// Delete with metadata
delete (todo.id, { metadata: { reason: "completed" } })
// Delete without optimistic updates (waits for server confirmation)
delete (todo.id, { optimistic: false })
// Delete with metadata and optimistic control
delete (todo.id,
{
metadata: { reason: "admin deletion" },
optimistic: false,
})
// Delete a single item
delete todo.id
// Delete multiple items
delete [todo1.id, todo2.id]
// Delete with metadata
delete (todo.id, { metadata: { reason: "completed" } })
// Delete without optimistic updates (waits for server confirmation)
delete (todo.id, { optimistic: false })
// Delete with metadata and optimistic control
delete (todo.id,
{
metadata: { reason: "admin deletion" },
optimistic: false,
})
默认情况下,所有更改(insert、update、delete)会立即应用乐观更新,以提供即时的 UI 反馈。但是,在某些情况下,您可能希望禁用此行为,并在本地应用更改之前等待服务器确认。
考虑禁用乐观更新,当
optimistic: true (默认):
optimistic: false:
// Example: Critical deletion that needs confirmation
const handleDeleteAccount = () => {
// Don't remove from UI until server confirms
userCollection.delete(userId, { optimistic: false })
}
// Example: Server-generated data
const handleCreateInvoice = () => {
// Server generates invoice number, tax calculations, etc.
invoiceCollection.insert(invoiceData, { optimistic: false })
}
// Example: Mixed approach in same transaction
tx.mutate(() => {
// Instant UI feedback for simple change
todoCollection.update(todoId, (draft) => {
draft.completed = true
})
// Wait for server confirmation for complex change
auditCollection.insert(auditRecord, { optimistic: false })
})
// Example: Critical deletion that needs confirmation
const handleDeleteAccount = () => {
// Don't remove from UI until server confirms
userCollection.delete(userId, { optimistic: false })
}
// Example: Server-generated data
const handleCreateInvoice = () => {
// Server generates invoice number, tax calculations, etc.
invoiceCollection.insert(invoiceData, { optimistic: false })
}
// Example: Mixed approach in same transaction
tx.mutate(() => {
// Instant UI feedback for simple change
todoCollection.update(todoId, (draft) => {
draft.completed = true
})
// Wait for server confirmation for complex change
auditCollection.insert(auditRecord, { optimistic: false })
})
此处我们演示了两种使用 TanStack DB 的常见方式
提示
您可以组合这些模式。TanStack DB 的好处之一是您可以在同一个应用中集成不同的数据加载和更改处理方式。您的组件无需了解数据来自何处或去往何处。
您可以通过 TanStack Query 将 TanStack DB 与现有的 REST API 结合使用。
步骤是
import { useLiveQuery, createCollection } from "@tanstack/react-db"
import { queryCollectionOptions } from "@tanstack/query-db-collection"
// Load data into collections using TanStack Query.
// It's common to define these in a `collections` module.
const todoCollection = createCollection(queryCollectionOptions({
queryKey: ["todos"],
queryFn: async () => fetch("/api/todos"),
getKey: (item) => item.id,
schema: todoSchema, // any standard schema
onInsert: async ({ transaction }) => {
const { changes: newTodo } = transaction.mutations[0]
// Handle the local write by sending it to your API.
await api.todos.create(newTodo)
}
// also add onUpdate, onDelete as needed.
}))
const listCollection = createCollection(queryCollectionOptions({
queryKey: ["todo-lists"],
queryFn: async () => fetch("/api/todo-lists"),
getKey: (item) => item.id,
schema: todoListSchema,
onInsert: async ({ transaction }) => {
const { changes: newTodo } = transaction.mutations[0]
// Handle the local write by sending it to your API.
await api.todoLists.create(newTodo)
}
// also add onUpdate, onDelete as needed.
}))
const Todos = () => {
// Read the data using live queries. Here we show a live
// query that joins across two collections.
const { data: todos } = useLiveQuery((q) =>
q
.from({ todo: todoCollection })
.join(
{ list: listCollection },
({ todo, list }) => eq(list.id, todo.list_id),
'inner'
)
.where(({ list }) => eq(list.active, true))
.select(({ todo, list }) => ({
id: todo.id,
text: todo.text,
status: todo.status,
listName: list.name
}))
)
// ...
}
import { useLiveQuery, createCollection } from "@tanstack/react-db"
import { queryCollectionOptions } from "@tanstack/query-db-collection"
// Load data into collections using TanStack Query.
// It's common to define these in a `collections` module.
const todoCollection = createCollection(queryCollectionOptions({
queryKey: ["todos"],
queryFn: async () => fetch("/api/todos"),
getKey: (item) => item.id,
schema: todoSchema, // any standard schema
onInsert: async ({ transaction }) => {
const { changes: newTodo } = transaction.mutations[0]
// Handle the local write by sending it to your API.
await api.todos.create(newTodo)
}
// also add onUpdate, onDelete as needed.
}))
const listCollection = createCollection(queryCollectionOptions({
queryKey: ["todo-lists"],
queryFn: async () => fetch("/api/todo-lists"),
getKey: (item) => item.id,
schema: todoListSchema,
onInsert: async ({ transaction }) => {
const { changes: newTodo } = transaction.mutations[0]
// Handle the local write by sending it to your API.
await api.todoLists.create(newTodo)
}
// also add onUpdate, onDelete as needed.
}))
const Todos = () => {
// Read the data using live queries. Here we show a live
// query that joins across two collections.
const { data: todos } = useLiveQuery((q) =>
q
.from({ todo: todoCollection })
.join(
{ list: listCollection },
({ todo, list }) => eq(list.id, todo.list_id),
'inner'
)
.where(({ list }) => eq(list.active, true))
.select(({ todo, list }) => ({
id: todo.id,
text: todo.text,
status: todo.status,
listName: list.name
}))
)
// ...
}
这种模式允许您为现有的 TanStack Query 应用程序,或任何基于 REST API 构建的应用程序,添加超快的跨集合实时查询和本地乐观更新,并自动管理乐观状态。
TanStack DB 最强大的用法之一是结合同步引擎,实现完全的本地优先体验并进行实时同步。这允许您将同步逐步集成到现有应用程序中,同时仍然通过现有 API 处理写入。
在这里,我们使用 ElectricSQL 作为同步引擎来演示这种模式。
import type { Collection } from '@tanstack/db'
import type { MutationFn, PendingMutation, createCollection } from '@tanstack/react-db'
import { electricCollectionOptions } from '@tanstack/electric-db-collection'
export const todoCollection = createCollection(electricCollectionOptions({
id: 'todos',
schema: todoSchema,
// Electric syncs data using "shapes". These are filtered views
// on database tables that Electric keeps in sync for you.
shapeOptions: {
url: 'https://api.electric-sql.cloud/v1/shape',
params: {
table: 'todos'
}
},
getKey: (item) => item.id,
schema: todoSchema,
onInsert: async ({ transaction }) => {
const response = await api.todos.create(transaction.mutations[0].modified)
return { txid: response.txid}
}
// You can also implement onUpdate, onDelete as needed.
}))
const AddTodo = () => {
return (
<Button
onClick={() =>
todoCollection.insert({ text: "🔥 Make app faster" })
}
/>
)
}
import type { Collection } from '@tanstack/db'
import type { MutationFn, PendingMutation, createCollection } from '@tanstack/react-db'
import { electricCollectionOptions } from '@tanstack/electric-db-collection'
export const todoCollection = createCollection(electricCollectionOptions({
id: 'todos',
schema: todoSchema,
// Electric syncs data using "shapes". These are filtered views
// on database tables that Electric keeps in sync for you.
shapeOptions: {
url: 'https://api.electric-sql.cloud/v1/shape',
params: {
table: 'todos'
}
},
getKey: (item) => item.id,
schema: todoSchema,
onInsert: async ({ transaction }) => {
const response = await api.todos.create(transaction.mutations[0].modified)
return { txid: response.txid}
}
// You can also implement onUpdate, onDelete as needed.
}))
const AddTodo = () => {
return (
<Button
onClick={() =>
todoCollection.insert({ text: "🔥 Make app faster" })
}
/>
)
}
当将 TanStack DB 与 React Native 结合使用时,您需要安装和配置一个 UUID 生成库,因为 React Native 默认不包含 crypto.randomUUID()。
安装 react-native-random-uuid 包
npm install react-native-random-uuid
npm install react-native-random-uuid
然后,在您的 React Native 应用的入口点(例如,在您的 App.js 或 index.js 文件中)导入它
import 'react-native-random-uuid'
import 'react-native-random-uuid'
此 polyfill 提供了 TanStack DB 内部用于生成唯一标识符的 crypto.randomUUID() 函数。
如果您在使用 TanStack DB 时有任何问题/需要帮助,请在 Discord 上告诉我们,或在 GitHub 上发起讨论
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。