将 TanStack DB 持久化到 IndexedDB 与 OPFS:基于 RxDB 存储层的浏览器持久化配置实战
【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址: https://gitcode.com/gh_mirrors/rx/rxdb
TanStack DB 默认把所有 collection 数据保存在内存中,页面刷新即丢失;持久化职责由你选择的 collection 实现来承担。官方@tanstack/rxdb-db-collection包把 RxDB(一款存储引擎可插拔的 local-first NoSQL 数据库)置于 TanStack DB collection 之下,于是"如何持久化 TanStack DB"就变成了在 localStorage、IndexedDB 与 OPFS 之间的配置选择。本文以 persist-tanstack-db-indexeddb.md 为主体,完整介绍浏览器端各存储选项的取舍、一套可运行的 localStorage 持久化示例,并展示切换到 IndexedDB 或 OPFS 时只需替换一行storage:配置的做法。读完本文,你将掌握 TanStack DB + RxDB 组合在浏览器端的全部持久化方案与切换技巧。
为什么 TanStack DB 需要一层存储
TanStack DB 是一个内存型响应式客户端 store。实时查询(live queries)与乐观更新(optimistic mutations)都作用在 JavaScript 内存中的数据上,这正是它们"即时"响应的原因。而持久化被明确委托给了 collection 类型:RxDB collection 用真正的数据库来填补这一角色——对 TanStack DB collection 的每次写入都会持久化到对应的 RxCollection,下一次页面加载时,collection 会从磁盘恢复自身状态。
RxDB 本身并不关心"磁盘"具体是什么。所有数据都经由 RxStorage 接口流动,你在创建数据库时选定具体实现。TanStack DB 代码永远不会直接触碰存储层,这正是"切换存储只是改配置、而不是重写代码"的根本原因。
从源码结构看,这一抽象非常直观:例如 getRxStorageLocalstorage 返回一个实现RxStorage接口的RxStorageLocalstorage实例,而createRxDatabase只依赖该接口。存储层的内部细节(如 RxStorageInstanceLocalstorage 把整个状态以字符串化 JSON 存进 localStorage)对上层完全透明。
TanStack DB 在浏览器中的存储选项
对浏览器应用而言,有四类存储值得关注:两类免费、两类属于 RxDB Premium 商业插件。
| 存储 | 价格 | 基于 | 适用场景 |
|---|---|---|---|
| LocalStorage 存储 | 免费 | localStorage API | 中小数据集、最简配置 |
| Dexie.js 存储 | 免费 | IndexedDB | 无 Premium 权限的较大数据集 |
| IndexedDB 存储 👑 | 付费 | IndexedDB | 生产应用、最低延迟、最小构建体积 |
| OPFS 存储 👑 | 付费 | Origin Private File System | 大数据集、最快查询 |
LocalStorage 存储(免费)
基于 localStorage 的存储是起步阶段的推荐默认项:无额外依赖、构建体积小、小数据集读写很快(性能对比见 localstorage-indexeddb-cookies-opfs-sqlite-wasm.md 的 performance-comparison 章节)。其代价是:浏览器通常把 localStorage 限制在每域名约 5 MB,且访问是主线程上的同步操作。对 todo 应用、设置存储或原型来说这完全够用;问题始于数据集增长到几千条文档之后。
源码层面,localStorage 存储把全部状态按 key 分桶存放——docsKey(文档)、attachmentsKey(附件)、changestreamStorageKey(变更流)、indexesKey(索引),见 rx-storage-instance-localstorage.ts。它还借助浏览器原生storage事件实现跨标签页的变更广播(storageEventStream),这也侧面说明它天然适配多标签页场景。
Dexie.js 存储(免费)
Dexie.js 存储通过 Dexie.js 封装库把数据写入 IndexedDB。IndexedDB 没有固定的 5 MB 上限,因此它是"无 Premium 权限、但数据集较大"时的免费选择,也支持使用 Dexie.js 生态的 addons。它的代价包括:控制台会打印一条指向 Premium 存储的提示信息;查询性能落后于 Premium 版 IndexedDB 存储。
IndexedDB 存储 👑
Premium 版 IndexedDB 存储直接构建在原生 IndexedDB 之上,不依赖任何封装库。相比 Dexie.js 存储,它的读写更快、构建体积最多可减少约36%,并运行在类似 SQLite 的 WAL(Write-Ahead Logging)模式下以获得更快写入。在浏览器存储中,它的写入/读取延迟最小、首次页面加载最快,是多数生产应用的默认选择。需要说明的是,这些性能结论以当前仓库文档 rx-storage-indexeddb.md 与 rx-storage-performance.md 的记载为准。
OPFS 存储 👑
Premium 版 OPFS 存储把数据写入Origin Private File System(OPFS)——一项让 Web 应用获得沙箱化、按源隔离的虚拟文件系统并提供字节级文件访问的浏览器 API。由于它操作的是二进制文件而非对象存储,复杂查询下的读取可比 IndexedDB 快最多 4 倍。OPFS 的高速同步方法只存在于 WebWorker 中,因此该存储默认运行在 worker 内,顺带把数据库工作移出主线程。当 TanStack DB collection 持有超过10k条文档时,OPFS 是更优选择;完整数据见 rx-storage-performance.md。
持久化 TanStack DB collection:可运行示例
下面以免费的 localStorage 存储为例,演示完整链路。集成基础(双循环架构、rxdbCollectionOptions配置项等)见 TanStack DB + RxDB 集成概述,此处只保留必要部分。
安装依赖
npm install rxdb rxjs @tanstack/react-db @tanstack/rxdb-db-collectionTanStack DB 还提供 Vue、Solid、Svelte、Angular 的官方绑定。由于rxdbCollectionOptions()面向框架无关的createCollection()API,RxDB collection 与所有官方框架绑定均兼容——例如 React 之外只需把useLiveQuery的导入换成@tanstack/vue-db。
创建持久化的 RxDatabase
import { createRxDatabase } from 'rxdb/plugins/core'; import { getRxStorageLocalstorage } from 'rxdb/plugins/storage-localstorage'; const db = await createRxDatabase({ name: 'todosdb', // 这一行决定了你的 TanStack DB 数据被持久化到哪里。 storage: getRxStorageLocalstorage() }); await db.addCollections({ todos: { schema: { title: 'todos', version: 0, type: 'object', primaryKey: 'id', properties: { id: { type: 'string', maxLength: 100 }, text: { type: 'string' }, completed: { type: 'boolean' } }, required: ['id', 'text', 'completed'] } } });schema是标准 RxDB JSON Schema:primaryKey声明主键字段,maxLength限制索引键长度,required强制必填字段。RxDB 会在写入时做模式校验,校验失败会导致写入被拒绝并回滚。
包装成 TanStack DB collection
import { createCollection } from '@tanstack/react-db'; import { rxdbCollectionOptions } from '@tanstack/rxdb-db-collection'; const todosCollection = createCollection( rxdbCollectionOptions({ rxCollection: db.todos }) );todosCollection从此就是一个标准的 TanStack DB collection:首次加载时从磁盘恢复初始状态,之后持续跟随 RxDB 的变化。rxdbCollectionOptions()还支持若干可选参数:id(collection 唯一标识)、schema(TanStack DB 侧的 Standard Schema 校验,由于 RxDB 已按 JSON Schema 校验,通常仅用于统一错误处理)、startSync(默认true,是否立即开始摄取 RxDB 数据)、syncBatchSize(默认1000,初始加载时每批拉取的文档数,只影响初始加载,后续变更通过 RxDB 变更流逐条流入)。
查询、变更并刷新验证
import { useLiveQuery, eq } from '@tanstack/react-db'; function OpenTodos() { const { data } = useLiveQuery((q) => q .from({ todo: todosCollection }) .where(({ todo }) => eq(todo.completed, false)) ); return <div>{data.length} open todos</div>; } // 写入即时作用于内存,并由 RxDB 持久化。 todosCollection.insert({ id: 'todo-1', text: 'buy milk', completed: false });写入在内存中即时生效并后台持久化到 RxDB;若持久化失败(如模式校验错误),TanStack DB 会回滚乐观状态。此时刷新页面:collection 会从存储中恢复状态,todo-1依然存在。这正是TanStack DB 持久化的意义所在——内存 store 之所以能扛过刷新,是因为 RxDB 在磁盘上拥有一份持久副本。
从源码看,TanStack DB collection 的写入最终走 RxDB 的批量写方法:insert 对应bulkUpsert()、update 对应incrementalPatch()、delete 对应bulkRemove()(详见 rxdb-collection-for-tanstack-db.md 的 Configuration Options 章节)。
切换到 IndexedDB 或 OPFS
上面的应用代码完全不需要改动,只需替换导入语句和createRxDatabase()里的storage:一行。
使用免费的 Dexie.js 存储(底层即 IndexedDB):
import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie'; const db = await createRxDatabase({ name: 'todosdb', storage: getRxStorageDexie() });使用 Premium 版 IndexedDB 存储 👑:
import { getRxStorageIndexedDB } from 'rxdb-premium/plugins/storage-indexeddb'; const db = await createRxDatabase({ name: 'todosdb', storage: getRxStorageIndexedDB() });使用 Premium 版 OPFS 存储 👑——它通过 worker 存储运行在 WebWorker 内:
import { getRxStorageWorker } from 'rxdb-premium/plugins/storage-worker'; const db = await createRxDatabase({ name: 'todosdb', storage: getRxStorageWorker({ // 该文件必须由你的 Web 服务器静态托管。 workerInput: 'node_modules/rxdb-premium/dist/workers/opfs.worker.js' }) });如果想省去 worker 部署,rxdb-premium/plugins/storage-opfs导出的getRxStorageOPFSMainThread()可用异步 OPFS API 在主线程运行 OPFS。两种变体的性能差异(worker 对顺序读更快,主线程对批量插入因免去消息序列化开销可能反而更快)对比见 rx-storage-opfs.md 的 using-opfs-in-the-main-thread-instead-of-a-worker 章节,建议针对实际用例分别实测。
需要注意:新的存储从空开始。如果在已有用户数据的应用中切换存储,请先用 storage migration 插件 把既有文档迁移过去。该插件会按批次(batchSize,示例为 500)把旧存储的文档复制进新数据库;它只迁移调用migrateStorage()时新数据库中已定义的 collection,并且会丢弃已删除文档;同时官方明确警告:不要在存储迁移过程中同时修改 schema,应"先迁移存储、再跑 schema 迁移"(见 migration-storage.md)。
TanStack DB 自带持久化方案对比
TanStack DB 也自带浏览器持久化:@tanstack/browser-db-sqlite-persistence适配器通过 wa-sqlite 把 collection 持久化到编译为 WebAssembly 的 SQLite。如果需求仅仅是"collection 在浏览器刷新后存活",该适配器完全够用,无需引入 RxDB。
而 RxDB collection 更适合"持久化只是第一步"的场景:
- 多种存储:同一套代码路径横跨 localStorage、IndexedDB、OPFS 与原生平台的 SQLite 存储,按环境选择。
- 复制同步:RxDB 的 Sync Engine 可把持久化数据与任意后端同步,包括 offline-first 行为。
- 加密:数据落盘前可先 加密本地数据。
- 多标签页:所有标签页共享同一份持久存储,并通过 leader election 保证复制只在一个标签页运行,而不是每个标签页各自持有一份状态。
FAQ
TanStack DB 默认会把数据持久化到 IndexedDB 吗?
不会。TanStack DB collection 是内存型的,页面刷新即丢失状态。持久化来自 collection 实现:使用RxDB collection时,通过配置对应的 RxStorage,即可持久化到 localStorage、IndexedDB 或 OPFS。
我可以免费把 TanStack DB 持久化到 IndexedDB 吗?
可以。免费的Dexie.js 存储会把 TanStack DB 数据存入 IndexedDB。Premium 版 IndexedDB 存储 👑 更快、构建体积更小,但获得可靠的 IndexedDB 持久化并不需要它。
对 TanStack DB 存储而言,OPFS 比 IndexedDB 快吗?
大数据集上的读取是的。OPFS 存储基于二进制文件工作,读取可比 IndexedDB 快最多 4 倍;而 IndexedDB 存储的首次页面加载更快、构建体积更小。因此当 collection 超过10k条文档时,OPFS 才真正划算。
切换 RxDB 存储会丢数据吗?
不会丢,但数据不会自己搬过去。旧存储保留文档、新存储从空开始。用storage migration 插件执行一次迁移把既有文档复制进新存储,再发布新配置即可。
存储选择会改变我的 TanStack DB 代码吗?
不会。TanStack DB 面向 RxCollection 编程,RxCollection 面向你配置的任意RxStorage编程。查询、变更与实时更新在任何存储上行为一致——切换存储是配置变更,不是重写。
延伸阅读
- 完整的集成指南见 TanStack DB + RxDB 集成概述。
- 从 RxDB 快速开始 起步。
- 在 RxStorage 总览 与 性能对比页 横向比较所有存储。
- 用同一套代码在原生平台使用 SQLite 存储。
- 在持久化数据之上叠加 后端同步。
【免费下载链接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/项目地址: https://gitcode.com/gh_mirrors/rx/rxdb
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考