news 2026/9/20 12:33:08

RxDB LocalStorage 存储引擎(RxStorage Localstorage):在浏览器中用最小配置实现本地持久化

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RxDB LocalStorage 存储引擎(RxStorage Localstorage):在浏览器中用最小配置实现本地持久化
  • 数据库
  • NoSQL
  • 嵌入式数据库
  • 实时数据库

【免费下载链接】rxdb

The 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 支持在多种底层存储之上运行,而基于浏览器原生 localStorage 的 RxStorage 是其中配置最简单、上手最快的一种:无需安装任何额外依赖,只要导入插件并传入getRxStorageLocalstorage(),就能获得完整的 RxDB 文档读写与查询能力。本文将以 rx-storage-localstorage.md 为骨架,结合仓库内 storage-localstorage 插件 的源码实现与 单元测试,带你掌握它的安装配置、数据模型、局限边界、Node.js 测试技巧,以及底层「键命名、跨标签页同步、索引查询」的完整原理。

为什么 LocalStorage 是浏览器场景的推荐默认

在 rx-storage.md 中,官方明确给出了各运行环境的选型建议:在浏览器中,优先使用 LocalStorage 存储以获得最简单的配置与最小的打包体积;只有当数据量变大时,才建议迁移到基于 IndexedDB 的实现(如 dexie.js 存储 或 IndexedDB RxStorage)。同时,在 Capacitor 这类移动容器环境中,LocalStorage 也被作为无 Premium 权限时的默认方案。

这一推荐背后有三个关键理由:

  1. 极致的简单性:localStorage 是浏览器内置 API,无需数据库文件、无需 Worker 启动、无需下载任何 WASM 二进制。对比 WASM SQLite 需要约半秒的下载与初始化时间(见 localstorage-indexeddb-cookies-opfs-sqlite-wasm.md 的初始化对比),LocalStorage 开箱即用、零初始化开销。
  2. 小数据集下读写很快:由于是同步内存映射的键值存储,对小型 JSON 文档的读写延迟极低(相关基准测试详见 性能对比文章)。文档中也特别提到,RxDB 甚至利用这一特性,把 localstorage-meta-optimizer 用作其他存储(如 IndexedDB)的元数据缓存层,用来加速数据库与集合的初始化。
  3. 零配置的搭建体验:只需import插件 + 把getRxStorageLocalstorage()传入createRxDatabase(),仅此两步即可开始读写文档。

因此,LocalStorage 非常适合演示项目、原型、小规模应用,也适合作为刚接触 RxDB 时的第一个存储后端。

需要了解的局限性

在开始之前,先明确 LocalStorage 的两个先天约束(官方文档明确列出,源码实现也与此直接相关):

  • 容量有限:浏览器通常将每个域名的 localStorage 限制在约 5 MB左右,具体上限因浏览器而异(相关细节见 localstorage.md)。这意味着它不适合存放大量文档或大体积附件。
  • 同步阻塞访问:localStorage 的所有操作都是同步的,会阻塞主线程。小数据量下影响不大,但高频、大批量的读写会造成性能瓶颈。从源码看,RxStorageInstanceLocalstorage 的所有读写都是同步调用localStorage.getItem/setItem,这与 IndexedDB/OPFS 的异步接口有本质区别。

此外还有两点值得注意的衍生限制:localStorage 只能存储字符串,所有文档必须经JSON.stringify序列化后写入、读取时再JSON.parse(见 localstorage.md);并且由于它是主线程 API,无法在 WebWorker / SharedWorker 中使用,这也是 RxDB 的 Worker 存储 与 SharedWorker 存储 只能基于 IndexedDB 等异步存储的原因。

快速上手:从零搭建一个 LocalStorage 数据库

官方文档给出了一套完整的五步入门流程,下面完整复现并补充必要的说明。

1. 导入存储插件

import { createRxDatabase } from 'rxdb/plugins/core'; import { getRxStorageLocalstorage } from 'rxdb/plugins/storage-localstorage';

其中getRxStorageLocalstorage定义在 src/plugins/storage-localstorage/index.ts,它接收一个可选的Partial<LocalstorageStorageSettings>参数并返回RxStorageLocalstorage实例。

2. 创建数据库

const db = await createRxDatabase({ name: 'exampledb', storage: getRxStorageLocalstorage() });

这里storage可以是任何实现了 RxStorage 接口 的实现,LocalStorage 实现只是其中之一。数据库创建完成后,你便拥有了一个完整的、具备响应式查询能力的 RxDB 数据库实例(关于 RxDatabase 的更多 API 见 rx-database.md)。

3. 添加集合(定义 Schema)

await db.addCollections({ tasks: { schema: { title: 'tasks schema', version: 0, primaryKey: 'id', type: 'object', properties: { id: { type: 'string', maxLength: 100 }, title: { type: 'string' }, done: { type: 'boolean' } }, required: ['id', 'title', 'done'] } } });

Schema 中primaryKey: 'id'指定了主键字段。从源码看,主键字段在存储实例创建时通过 getPrimaryFieldOfPrimaryKey 解析,并会自动为[primaryPath]追加一个索引,同时额外维护一个清理索引['_deleted', '_meta.lwt'](见 createLocalstorageStorageInstance)。也就是说,即使你不在 schema 里声明indexes,主键索引与删除清理索引也是始终存在的。

4. 插入文档

await db.tasks.insert({ id: 'task-01', title: 'Get started with RxDB', done: false });

5. 查询文档

const nonDoneTasks = await db.tasks.find({ selector: { done: { $eq: false } } }).exec();

find().exec()会返回满足done === false的所有文档数组。得益于存储层对索引与排序的支持,这类查询会走索引二分查找(详见下文「查询与索引」小节),而不是全量扫描。

在 Node.js 中用 Mock 进行单元测试

localStorageAPI 只存在于浏览器环境。如果你要在 Node.js 中跑单元测试,可以直接使用 RxDB 自带的内存 Mock 实现getLocalStorageMock(),它导出自 localstorage-mock.ts:

import { createRxDatabase } from 'rxdb/plugins/core'; import { getRxStorageLocalstorage, getLocalStorageMock } from 'rxdb/plugins/storage-localstorage'; const db = await createRxDatabase({ name: 'exampledb', storage: getRxStorageLocalstorage({ localStorage: getLocalStorageMock() }) });

从源码看,getLocalStorageMock() 用一个普通对象storage模拟了setItemgetItemremoveItemlengthkey五个标准接口。值得注意的是,它的setItem在写入后会向内部的storageEventStream$发射一条fromStorageEvent: true的事件——这是为了模拟浏览器的跨标签页storage事件,从而让「同一实例内的变化也能触发变更流」,方便在单进程测试中验证响应式行为(源码注释也明确说明了这一设计意图)。

settings.localStorage的注入点在 RxStorageInstanceLocalstorage 构造函数:settings.localStorage ? settings.localStorage : window.localStorage。因此你可以传入任何兼容typeof localStorage的对象(Mock、node-localstorage包等),这为在非浏览器运行时做测试提供了极大的灵活性。

仓库的 rx-storage-localstorage.test.ts 本身就是这套用法的实战范例:测试使用getLocalStorageMock()配合wrappedValidateAjvStorage(AJV 校验包装)创建数据库,验证db.remove()后所有带数据库名前缀的键(含附件键)都被清除干净。

源码解析:数据究竟存在哪里

理解键的命名规则,能帮你直观地在浏览器 DevTools 中核查数据。在 RxStorageInstanceLocalstorage 构造函数 中,每个存储实例会基于databaseName + collectionName + schema.version生成五类键前缀:

键前缀用途
RxDB-ls-doc-<db>--<collection>--<version>每个文档一个键,值为JSON.stringify后的完整文档数据
RxDB-ls-changes-<db>--<collection>--<version>变更流存储,值为最近一次批量变更事件(含 checkpoint)的 JSON
RxDB-ls-idx-<db>--<collection>--<version>索引数据,值为「索引字符串 + 文档 id」二元组的数组 JSON
RxDB-ls-attachment-<db>--<collection>--<version>附件数据,以data:<mime>;base64,<payload>的 Data URL 形式存储

文档写入路径bulkWrite(源码)的流程也很有代表性:

  1. 预处理附件:先将所有Blob附件异步转换为 base64,因为 localStorage 没有事务且是同步 API,冲突检查之后的所有写入必须保持同步,避免交错(源码注释明确说明了这一点)。
  2. 冲突归类:通过categorizeBulkWriteRows区分新增、更新与错误行,冲突的文档会进入ret.error
  3. 写文档 + 维护索引setDoc序列化写入文档;同时用pushAtSortPosition将新索引串按序插入索引数组,并处理索引未变化时的性能快捷路径。
  4. 写变更流:若产生了变更事件,会把eventBulk连同databaseInstanceToken一起序列化写入changestreamStorageKey,并向storageEventStream$广播。

跨标签页同步:storage 事件与变更流

LocalStorage 存储的跨标签页响应式能力,是它区别于 IndexedDB 的一个独特优势(IndexedDB 没有对应的观测事件)。浏览器会在其他标签页写入 localStorage 时触发storage事件。RxDB 在 getStorageEventStream 中注册了全局的window.addEventListener('storage', ...),把外部写入转发到内部的storageEventStream$Subject。

存储实例的变更流订阅(构造函数)则会过滤事件:只关注changestreamStorageKey的写入,并跳过「由同一实例产生」的事件(通过databaseInstanceToken比对),从而避免把本实例写入的事件重复派发给自己。这样,多个标签页打开同一个数据库时,任何一方的写操作都会通过 storage 事件驱动其他标签页的changes$变更流,让find().$等响应式查询自动收到更新——这正是 RxDB 多实例同步(见 rx-storage-multiinstance.ts)在浏览器中的底层支撑之一。

查询与索引:二分查找 + 手动重排序

虽然 localStorage 本身没有索引能力,RxDB 却在之上自建了索引层。每个查询在执行前会先生成查询计划(queryPlan),存储层据此:

  1. getStartIndexStringFromLowerBound/getStartIndexStringFromUpperBound(来自 custom-index.ts)把查询边界编码为索引字符串;
  2. 在索引数组上通过boundGE/boundGT/boundLE/boundLT(来自 storage-memory 的二分查找)定位上下界,避免全表扫描
  3. 逐条取出文档并交给queryMatcher过滤(当选择器不能完全由索引满足时);
  4. 若排序无法由索引满足(mustManuallyResort),则用getSortComparator在内存中重新排序;
  5. 最后应用skiplimit边界。

完整的查询实现见 query 方法。此外,count()直接复用query()结果计算数量并返回mode: 'fast',测试 rx-storage-localstorage.test.ts 专门验证了「带 limit 时count()必须与query().documents.length一致」这一契约。

附件支持与数据清理

  • 附件:LocalStorage 存储支持 RxAttachment。写入时附件被转换为data:<mime>;base64,<payload>字符串存储(bulkWrite中的attachmentsAdd/attachmentsUpdate/attachmentsRemove分支);读取时getAttachmentData通过fetch(dataUrl).blob()还原为Blob,并对旧的裸 base64 载荷做了向后兼容兜底(见 getAttachmentData)。考虑到 5 MB 的容量上限,附件功能更适合小文件场景。
  • 清理(cleanup):存储层实现了cleanup(minimumDeletedTime),通过专用索引['_deleted', '_meta.lwt']按写入时间戳lwt批量移除过期删除标记及其索引项(见 cleanup 实现)。这为 RxDB 的 cleanup 插件 提供了底层支撑。
  • 移除与关闭remove()会卸载变更流订阅,并逐一删除该集合的文档、附件、索引与变更流键;close()则完成变更流并清除变更存储(见 remove/close)。

什么时候该迁移到 IndexedDB

LocalStorage 的优势是「快、简单」,但当应用出现以下信号时,就应该考虑迁移到 IndexedDB RxStorage 或 Dexie 存储:

  • 数据必须大规模可查询:localStorage 键值模型 + 5 MB 上限,无法承载需要复杂查询或范围查询的大型数据集(对比分析见 localstorage.md);
  • 大数据量 JSON 文档:序列化与解析开销会放大读写延迟;
  • 高频读写:同步阻塞主线程会拖慢界面响应;
  • 需要超大容量:IndexedDB 通常可以按磁盘空间弹性扩展(Chromium 系最多可用约 80% 的磁盘),而 localStorage 被限制在个位数 MB 级别(详见 localstorage-indexeddb-cookies-opfs-sqlite-wasm.md 中的容量对比);
  • 需要在 WebWorker 中运行存储:localStorage 无法在 Worker 中访问,此时必须使用基于异步存储的 Worker 存储。

需要说明的是,localStorage 的写入延迟比 IndexedDB 低约一个数量级(相关实测见性能对比文章),但这一优势只在小数据集前提下成立;数据量上来后,容量与阻塞问题会迅速抵消它的速度优势。

小结

LocalStorage 存储是 RxDB 在浏览器中最轻量的落地方式:安装即用、零外部依赖,并且通过索引层、storage 事件变更流、附件 Data URL 存储等源码级设计,提供了超出原生 localStorage 能力的「类数据库」体验。结合 rx-storage.md 的选型建议:新项目、原型与轻量应用,直接使用getRxStorageLocalstorage()起步;当数据规模与查询复杂度上升时,再平滑迁移到 IndexedDB 系存储——这正是 RxDB「存储可替换」架构(RxStorage 接口)带来的核心价值。若想进一步了解存储抽象与性能对比,可继续阅读 rx-storage.md、rx-storage-performance.md 与 slow-indexeddb.md。

  • 数据库
  • NoSQL
  • 嵌入式数据库
  • 实时数据库

【免费下载链接】rxdb

The 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/20 12:30:14

温室温湿度 PID 闭环控制:STM32/ESP32 增量式实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/20 12:28:38

Vue3 + Three.js 智慧校园三维可视化实战:从选型到性能优化

简介&#xff1a;基于 Vue3 与 three.js 打造的智慧校园 3D 可视化前端项目源码&#xff0c;面向具备前端基础、希望系统学习 Web 三维开发的工程师和学习者&#xff0c;可帮助快速搭建可交互的校园场景&#xff0c;并理解从模型加载、场景构建到用户交互的完整实现链路。压缩包…

作者头像 李华
网站建设 2026/9/20 12:27:48

ASP.NET在线选课系统开发实践与架构设计

1. 项目概述与背景作为一名从事教育信息化系统开发多年的工程师&#xff0c;我最近完成了一个基于ASP.NET框架的在线选课系统开发项目。这个系统是为某高校设计的&#xff0c;旨在解决传统纸质选课方式效率低下、信息不透明的问题。系统采用B/S架构&#xff0c;前端使用HTML5CS…

作者头像 李华
网站建设 2026/9/20 12:27:02

Bootstrap图书商城模板实战:从解压运行到样式定制与后台对接

简介&#xff1a;面向Web前端开发学习者与商城类项目初建者&#xff0c;这份基于Bootstrap的图书商城前台页面模板&#xff0c;能够帮助快速搭起图书展示、商品陈列、页面导航等前台界面&#xff0c;也适合用来练习Bootstrap响应式布局和组件定制。压缩包内共649个文件&#xf…

作者头像 李华