RxDB 的 Dexie.js RxStorage 实战指南:基于 IndexedDB 的浏览器存储、Node.js Polyfill 与后端同步方案
【免费下载链接】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
在浏览器中使用 RxDB 时,官方默认推荐使用基于 Dexie.js 与 单元测试 解释其底层原理、局限与取舍。读完本文,你将能在浏览器侧项目、单元测试以及需要 Node 环境执行的场景中正确选用与配置该存储,并判断何时应切换到官方 Premium IndexedDB RxStorage。
Dexie.js RxStorage 是什么
RxDB 是一个本地优先(local-first)数据库,核心设计是可插拔的 RxStorage 抽象层:同一个数据库 API 可以运行在内存、IndexedDB、SQLite、MongoDB 等不同后端之上。Dexie.js RxStorage 就是这一抽象层在浏览器 IndexedDB 上的实现,其导出的工厂函数为getRxStorageDexie(),存储名称为dexie(见 dexie-helper.ts 中的RX_STORAGE_NAME_DEXIE)。
官方对它的定位非常明确:面向浏览器中的小项目与原型,Dexie RxStorage 应作为默认选择(见 rx-storage-dexie.md)。它的优点是接入成本极低、生态成熟(可复用 Dexie.js 庞大的 addon 插件体系),缺点是相对官方 Premium IndexedDB RxStorage 在读写性能与构建体积上存在差距。
Dexie.js vs IndexedDB Storage:何时应该切换
虽然 Dexie.js RxStorage 可以免费使用,但官方建议大多数专业项目在生产环境中切换到Premium IndexedDB RxStorage,原因如下:
- 更快且构建体积最多减少 36%:Premium 实现绕过了 Dexie 封装层,减少了代码量与间接调用。
- 读写性能更好:官方有专门的 性能文档 佐证。
- 附件存储为二进制而非 base64:可减少约 33% 的占用空间,详见 rx-attachment。
- 不使用 Batched Cursor 或自定义索引:Dexie 方案在这两点上会拖慢查询,相关背景见 slow-indexeddb。
- 支持非必填索引:这是 Dexie.js 做不到的(见下文“非必填索引限制”一节)。
- 类 WAL 写入模式:类似 SQLite 的写入方式,写入更快、响应性更好。
- 支持 Storage Buckets API:可精细管理存储配额,见 rx-storage-indexeddb 文档。
在阅读后续内容时请牢记这一取舍:本文方案适合快速原型与中小型项目,追求极致性能与生产级能力时,应评估 Premium 方案。
快速上手:引入 Dexie Storage 并创建数据库
Dexie RxStorage 的引入非常直接,使用官方rxdb/plugins/core与rxdb/plugins/storage-dexie两个入口:
import { createRxDatabase } from 'rxdb/plugins/core'; import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie'; const db = await createRxDatabase({ name: 'exampledb', storage: getRxStorageDexie() });在 package.json 中可以看到该插件的包导出配置:dist/types/plugins/storage-dexie/index.d.ts(类型)、dist/cjs与dist/esm两份产物,ESM 为默认入口,同时兼容 CommonJS 与浏览器打包器。
从源码结构看,getRxStorageDexie()内部会构造一个RxStorageDexie实例并持有用户传入的DexieSettings(见 rx-storage-dexie.ts)。每个 RxStorage 实例在创建时都会通过ensureRxStorageInstanceParamsAreCorrect()做参数校验(见 rx-storage-dexie.ts)。
非必填索引限制
一个需要特别注意的限制:Dexie.js不支持非必填索引。在RxStorageDexie.createStorageInstance()中,如果 schema 里声明了索引字段但该字段没有出现在required数组中,会直接抛出错误码DXE1(见 rx-storage-dexie.ts)。对应测试见 rx-storage-dexie.test.ts。因此使用本存储时,凡是需要建索引的字段都必须设为必填。
在 Node.js 中运行:用 fake-indexeddb Polyfill IndexedDB
Node.js 本身没有 IndexedDB API,因此 Dexie RxStorage 无法直接运行。官方推荐使用 fake-indexeddb 模块进行 Polyfill,并把实例传给getRxStorageDexie():
import { createRxDatabase } from 'rxdb/plugins/core'; import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie'; //> npm install fake-indexeddb --save const fakeIndexedDB = require('fake-indexeddb'); const fakeIDBKeyRange = require('fake-indexeddb/lib/FDBKeyRange'); const db = await createRxDatabase({ name: 'exampledb', storage: getRxStorageDexie({ indexedDB: fakeIndexedDB, IDBKeyRange: fakeIDBKeyRange }) });DexieSettings实际上继承了 Dexie 的DexieOptions,因此indexedDB与IDBKeyRange等选项会直接传给 Dexie 构造函数(类型定义见 types/plugins/dexie.d.ts)。
这一能力也直接服务于仓库的测试体系:fake-indexeddb(版本 6.2.5)是 devDependency(见 package.json),单元测试 rx-storage-dexie.test.ts 正是用fake-indexeddb的indexedDB作为测试环境;仓库还提供了大量以DEFAULT_STORAGE=dexie运行测试的脚本,例如test:fast:dexie、test:node:dexie、test:browser:dexie(见 package.json)。这意味着你可以用同样的方式在自己的 Node.js 单元测试或服务端脚本中完整运行 Dexie RxStorage。
使用 Dexie Addons:复用 Dexie.js 插件生态
Dexie.js 拥有自己的插件体系(addons),覆盖加密、复制等场景。使用 Dexie RxStorage 时,可以把这些插件通过addons选项传入:
const db = await createRxDatabase({ name: 'exampledb', storage: getRxStorageDexie({ addons: [ /* Your Dexie.js plugins */ ] }) });在源码中,addons 同样属于DexieOptions的一部分:getDexieDbWithTables()会以new Dexie(dexieDbName, useSettings)创建底层 Dexie 实例,useSettings是对用户 settings 的浅拷贝并强制设置autoOpen = false(见 dexie-helper.ts),从而保证数据库的打开时机由 RxStorage 内部控制。
还有一个值得关注的设计点:由于 IndexedDB 不适合动态建表,Dexie RxStorage为每个 RxStorage 实例创建一个独立的 Dexie 数据库,命名为rxdb-dexie-<databaseName>--<schema.version>--<collectionName>(见 dexie-helper.ts),每个库内包含docs、changes、attachments三张表(常量定义见 dexie-helper.ts),并配合引用计数实现连接复用与正确关闭(见 closeDexieDb)。这解释了为什么一个 RxDB 数据库中多个 collection 会对应多个底层 IndexedDB 数据库。
与后端同步的两种路线
本地数据与远端后端保持同步是 RxDB 的核心能力。使用 Dexie RxStorage 时有两种典型路线:
- Dexie Cloud:官方托管方案,适合快速搭建,开箱即用地提供云端后端与冲突解决。
- RxDB 原生 replication:对后端、数据流与 冲突处理 拥有完全控制权。
两条路线各有适用场景:追求快速起步可选 Dexie Cloud;需要自主可控与高可定制性则选 RxDB 原生复制。
A. 使用 Dexie Cloud 同步
Dexie Cloud是 Dexie 团队提供的官方 SaaS 方案,开箱即用地提供自动同步、用户管理与冲突解决:
- 自动同步:本地 IndexedDB 与云端后端自动保持同步。
- 用户认证:内置用户管理(认证、角色、权限)。
- 冲突解决:服务端自动处理冲突。
npm install dexie-cloud-addonimport { createRxDatabase } from 'rxdb/plugins/core'; import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie'; import dexieCloud from 'dexie-cloud-addon'; const storage = getRxStorageDexie({ addons: [dexieCloud], /* * Whenever a new dexie database instance is created, * this method will be called. */ async onCreate(dexieDatabase, dexieDatabaseName) { await dexieDatabase.cloud.configure({ databaseUrl: "https://<yourdatabase>.dexie.cloud", requireAuth: true // optional }); } }); const db = await createRxDatabase({ name: 'mydb', storage });注意onCreate回调正是DexieSettings中额外扩展的字段(见 types/plugins/dexie.d.ts),其执行时机在new Dexie(...)之后、dexieDb.version(1).stores(...)建表之前(见 dexie-helper.ts),因此非常适合做 Dexie 级配置(如 Cloud 初始化、addon 配置)。
B. 使用 RxDB 原生复制
如果需要最大灵活性,可以选择 RxDB 众多复制插件之一:
- CouchDB 复制:与 CouchDB 服务器同步。
- GraphQL 复制:对接任意 GraphQL 端点,适合自定义 schema 或需要 GraphQL 查询能力时。
- 基于 REST API 的自定义复制:通过 pull/push handler 对接任意 RESTful 后端。
下面以 CouchDB 为例展示完整链路:创建数据库 → 添加 collection → 启动复制。
import { replicateCouchDB } from 'rxdb/plugins/replication-couchdb'; import { getRxStorageDexie } from 'rxdb/plugins/storage-dexie'; import { createRxDatabase } from 'rxdb/plugins/core'; const db = await createRxDatabase({ name: 'mydb', storage: getRxStorageDexie() }); await db.addCollections({ humans: { schema: { version: 0, type: 'object', primaryKey: 'id', properties: { id: { type: 'string', maxLength: 100 }, name: { type: 'string' }, age: { type: 'number' } }, required: ['id', 'name'] } } }); const replicationState = replicateCouchDB({ replicationIdentifier: 'my-couchdb-replication', collection: db.humans, // The URL to your CouchDB endpoint url: 'http://example.com/db/humans' });仓库在 test/replication-couchdb.test.ts 中正是以DEFAULT_STORAGE=dexie运行 CouchDB 复制测试(见 package.json),说明该组合是经过完整测试验证的成熟路径。
liveQuery 与 RxDB 的响应式查询
Dexie.js 提供liveQuery特性,可在数据变化时自动刷新查询结果。但 RxDB 本身内置 响应式查询,通常无需启用 Dexie 的 liveQuery:
collection.find().$.subscribe(results => { /* ... 每当结果变化时自动触发 ... */ });RxDB 会监听变更并自动推送新结果,UI 无需额外插件或手动轮询即可保持同步。这一机制在底层由 Dexie RxStorage 的changeStream()(返回changes$Subject 的可观察流)驱动,写入完成后会发出带 checkpoint 的变更事件(见 rx-storage-instance-dexie.ts 与 bulkWrite 中的事件发布)。
底层实现要点:查询、布尔索引与附件存储
Mango 查询如何在 Dexie 上执行
Dexie RxStorage 的查询逻辑位于 dexie-query.ts:它会根据查询计划(queryPlan)把 RxDB 的 Mango 查询转换成 IndexedDB 的IDBKeyRange(见 getKeyRangeByQueryPlan),然后通过底层 IndexedDB 事务打开复合索引游标逐条读取;如果查询条件未被索引完全满足,还会用getQueryMatcher()在内存中过滤;如果排序未被索引满足,则用getSortComparator()在内存中排序(见 dexieQuery)。游标在queryPlan.sortSatisfiedByIndex且已收集够skip + limit条时提前终止,避免全表扫描。count()则优先走索引的index.count(keyRange)快速路径(见 dexieCount)。
布尔索引与 key-compression 字段转义
IndexedDB 不支持布尔类型索引,因此 Dexie RxStorage 会把布尔值写为字符串'1'/'0'存储,查询范围时再映射回来(见 dexie-helper.ts 的 fromStorageToDexie/fromDexieToStorage 与 dexie-query.ts 的 rangeFieldToBooleanSubstitute)。
另外,若启用了 key-compression 插件,字段名可能以管道符|开头,而 IndexedDB 不允许这类键名,因此写入时会把|替换为__(DEXIE_PIPE_SUBSTITUTE),读回时再还原(见 dexie-helper.ts)。以上两套转换均有对应单元测试覆盖(见 rx-storage-dexie.test.ts)。
Store Schema 的自动生成
Dexie 的 store schema 字符串由getDexieStoreSchema()生成(见 dexie-helper.ts):主键排第一位,随后是_deleted+primaryKey复合索引、用户声明的索引、以及_meta.lwt+primaryKey(支撑getChangedDocumentsSince)与_meta.lwt(支撑 cleanup)等内部索引,最终转换为 Dexie 的[field1+field2]复合索引语法。
附件存储
附件的二进制数据存放在独立的attachments表中,以documentId + '||' + attachmentId为键(见 attachmentObjectId),写入与删除在bulkWrite的同一个 Dexie 事务中完成(见 rx-storage-instance-dexie.ts),读取走getAttachmentData()(见 rx-storage-instance-dexie.ts)。这也是前文提到 Premium 方案能以二进制替代 base64 从而节省约 33% 空间的原因之一。
关闭非 Premium 控制台日志
Dexie RxStorage 是免费的开源实现,首次写入时会在控制台输出一条提示信息,告知社区存在更快的 Premium 存储方案。该日志在 bulkWrite 中只输出一次(shownNonPremiumLog标志控制),并且会先检查hasPremiumFlag()。如果你已购买 Premium 并希望关闭该日志,可以调用:
import { setPremiumFlag } from 'rxdb-premium/plugins/shared'; setPremiumFlag();性能对比
Dexie.js RxStorage 的性能足以应对大多数场景,但其他存储方案(如内存存储、Premium IndexedDB)在特定操作上可能明显更优。下图是官方在 Chrome 浏览器环境对各 RxStorage 的基准对比(纵轴为毫秒,越低越好),可直观看到 Dexie.js 在批量插入等操作上与内存型/IndexedDB 方案的差距:
从图表可以观察到:memory与IndexedDB-memory-mapped在多数操作中耗时极低;dexie.js在Bulk insert 500 docs等批量操作中表现尚可,但相比 Premium IndexedDB 方案在读取、并行查询等操作上仍有差距。这正是官方建议生产环境评估 Premium IndexedDB RxStorage 的原因。仓库还提供了性能基准脚本test:performance:dexie(见 package.json),可自行在目标环境复测。
FAQ
Dexie.js 是什么?相比原生 IndexedDB 有什么优势?
Dexie.js 是专为解决原生 IndexedDB 复杂回调式 API 而设计的极简 Promise 封装。它提供直观可链式调用的查询 API、更简单的数据库 schema 定义方式,以及非常健壮的事务管理。但 Dexie 缺少文档型 NoSQL 数据库的高级查询能力(如深层嵌套 JSON 查询、完整的 MongoDB 风格 selector)。而 RxDB 把 Dexie 作为底层存储引擎使用,弥补了这些不足:RxDB 提供完全响应式的高级 NoSQL 查询引擎、跨平台离线复制协议,以及 Dexie 本身不具备的内置字段加密能力。
总结
Dexie.js RxStorage 是浏览器端接入 RxDB 最便捷的存储方案:一条工厂函数即可建库,天然支持响应式查询、附件与复制协议,可通过fake-indexeddb在 Node.js 中运行测试,还能复用 Dexie addon 生态(包括 Dexie Cloud)。理解它的底层实现——每实例一库的表结构、布尔索引与 key-compression 转义、基于查询计划的 IndexedDB 游标扫描——有助于你在真实项目中预判行为与排查问题。同时务必记住两个关键边界:索引字段必须为必填,且在小项目与原型之外,生产环境应评估官方 Premium IndexedDB RxStorage 以获得更好的性能与更小的体积。
【免费下载链接】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),仅供参考