多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

使用 Ionic 8 + Capacitor 构建跨端 RxDB 本地优先应用:Heroes 示例全解析

使用 Ionic 8 + Capacitor 构建跨端 RxDB 本地优先应用:Heroes 示例全解析 使用 Ionic 8 Capacitor 构建跨端 RxDB 本地优先应用Heroes 示例全解析【免费下载链接】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本篇技术指南围绕仓库中的 Ionic 示例应用 展开完整讲解如何在一个同时运行于浏览器、Android 与 iOS 的 Ionic 应用中集成 RxDB从环境搭建、数据库服务封装、类型化 Schema、响应式查询到 Leader Election 多标签页协同与原生端存储方案切换。读完本文你将掌握一套可复用的本地优先local-first移动端数据层搭建方案。Ionic 示例应用运行截图示例应用概览examples/ionic是一个经典的 Heroes 列表应用它演示了在IonicAngular应用中如何嵌入 RxDB并借助Capacitor同时打包为 Android 与 iOS 原生应用。应用的核心技术栈见 package.jsonIonic 8 Angular 22全部使用 standalone 组件ionic/angular/standaloneCapacitor 8capacitor/core、capacitor/android、capacitor/ios负责原生打包localstorage RxStorage作为存储层浏览器与 Capacitor WebView 内均可直接工作响应式查询列表会在英雄被插入、修改或删除的瞬间自动刷新多标签页支持在浏览器中同时打开两个标签页两边的列表会实时同步更新。从目录结构看应用主体位于 examples/ionic/srcapp/pages/home是入口页面app/components下拆分出heroes-list列表、hero-insert新增与hero-edit编辑三个组件app/schemas/hero.schema.ts定义数据类型app/services/database.service.ts封装数据库的创建与访问。环境搭建与启动在浏览器中直接运行仓库中已内置了完整的 Angular 工程配置克隆仓库后按以下步骤即可启动cd examples/ionic npm install npm run devnpm run dev实际执行ng serve --port 8100见 package.json随后打开 http://localhost:8100/ 即可看到应用界面。生成并打开原生工程原生工程Android / iOS没有随仓库提交首次需要手动生成npm run build npm run cap:add:android # 或: npm run cap:add:ios之后即可在原生 IDE 中打开工程npm run cap:open:android # 或: npm run cap:open:ios这两个脚本在执行npx cap open之前都会先完成 Web 构建并运行npx cap synccap:open:*内部调用npm run cap:sync即npm run build npx cap sync。Capacitor 的工程信息定义在 capacitor.config.ts 中import type { CapacitorConfig } from capacitor/cli; const config: CapacitorConfig { appId: info.rxdb.example.ionic, appName: RxDB Ionic Heroes, webDir: dist/ionic/browser }; export default config;appId原生工程的应用标识打包上架时需替换为自己的域名反向标识appName显示在设备桌面上的应用名称webDir指向ng build的输出目录npx cap sync会把这个目录里的产物同步进原生工程。关于依赖安装的说明示例的package.json中rxdb依赖被声明为file:rxdb-local.tgz由preinstall脚本npm run preinstall:rxdb执行仓库根目录的yarn pack在安装前本地打包生成——这是为了在 GitHub CI 中始终测试当前源码构建的 RxDB。在你的自有应用中请直接从 npm 安装正式发布版npm install rxdb rxjs数据库服务创建、类型化与生命周期管理数据库的创建集中在 database.service.ts它承担了环境差异处理、插件注册与初始化时序三个关键职责。按运行环境选择插件与模式const isNative Capacitor.isNativePlatform(); async function _create(): PromiseRxHeroesDatabase { if (isDevMode()) { const { RxDBDevModePlugin } await import(rxdb/plugins/dev-mode); addRxPlugin(RxDBDevModePlugin); } if (!isNative) { addRxPlugin(RxDBLeaderElectionPlugin); } const db: RxHeroesDatabase await createRxDatabase({ name: DATABASE_NAME, // heroesdb storage: getStorage(), multiInstance: !isNative }); // ... }三个要点dev-mode 插件仅在isDevMode()时为真且通过动态import()按需加载。它会在运行期添加大量校验Schema 校验、查询合法性检查等明显拖慢性能注释中明确提示生产环境切勿使用Leader Election 插件只注册在非原生环境。注释解释了原因原生平台上只有一个 WebView不存在多个标签页竞争的问题因此无需协调而浏览器中可能出现多标签页需要选出一个领导者统一执行写操作multiInstance: !isNative与插件注册保持一致——浏览器中允许多实例协同原生端关闭。保证应用启动前数据库就绪initDatabase()用模块级变量initState做了一次性初始化记忆并在 main.ts 中通过provideAppInitializer(() initDatabase())注册为应用初始化器bootstrapApplication(AppComponent, { providers: [ provideIonicAngular({ mode: md }), provideRouter(routes), { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }, provideAppInitializer(() initDatabase()), DatabaseService ] });这样组件渲染时DatabaseService.db一定已经可用组件代码可以同步拿到数据库实例(window as any)[db] db还把实例挂到全局方便在浏览器控制台里直接调试。全局钩子写入前的业务校验数据库创建后注册了一个preInsert钩子插入英雄前先按color字段查询若颜色已被占用则抛错从而保证颜色唯一该约束写不进 JSON Schema只能靠钩子实现db.hero.preInsert(async docData { const hasColor await db.hero.findOne({ selector: { color: docData.color } }).exec(); if (hasColor) { throw new Error(another hero already has the color docData.color); } }, false);第二个参数false表示钩子不带overwrite语义错误会直接向上抛出被 hero-insert.component.ts 的try/catch捕获后以 Ionic Toast 形式提示用户。类型化 Schema从字面量到完整的 TS 类型hero.schema.ts 演示了 RxDB 的类型化 Schema推荐写法export const HERO_SCHEMA_LITERAL { title: hero schema, description: describes a simple hero, version: 0, primaryKey: name, type: object, properties: { name: { type: string, maxLength: 100 }, color: { type: string, minLength: 3 }, hp: { type: number, minimum: 0, maximum: 100, multipleOf: 1, default: 100 } }, required: [name, color, hp] } as const; const schemaTyped toTypedRxJsonSchema(HERO_SCHEMA_LITERAL); export type RxHeroDocumentType ExtractDocumentTypeFromTypedRxJsonSchematypeof schemaTyped; export const HERO_SCHEMA: RxJsonSchemaRxHeroDocumentType HERO_SCHEMA_LITERAL;字段约束一览字段类型约束说明namestringmaxLength: 100同时是主键primaryKey天然唯一colorstringminLength: 3配合preInsert钩子保证颜色唯一hpnumber0 hp 100且为整数默认100multipleOf: 1等价于整数约束通过toTypedRxJsonSchemaExtractDocumentTypeFromTypedRxJsonSchemaTypeScript 可以从 Schema 字面量推导出文档类型RxHeroDocumentType再在 RxDB.d.ts 中逐层组合出集合与数据库的完整类型export type RxHeroDocument RxDocumentRxHeroDocumentType; export type RxHeroCollection RxCollectionRxHeroDocumentType; export type RxHeroesCollections { hero: RxHeroCollection }; export type RxHeroesDatabase RxDatabaseRxHeroesCollections;这样db.hero.insert({...})、查询结果等操作都能获得端到端的类型检查。响应式查询列表随数据自动刷新heroes-list.component.ts 是响应式能力的核心示范。组件用一个查询的.$可观察对象直接驱动模板配合 Angular 的AsyncPipe无需任何手动刷新逻辑heroes$: ObservableRxHeroDocument[] this.dbService.db.hero .find({ selector: {}, sort: [{ name: asc }] }) .$;只要集合中任何匹配文档被插入、更新或删除heroes$就会发出新数组模板自动重渲染。修改与删除同样直接走 RxDB 的文档 APIasync editHero(hero: RxHeroDocument) { const modal await this.modalController.create({ component: HeroEditComponent, componentProps: { hero } }); await modal.present(); } async deleteHero(hero: RxHeroDocument) { await hero.remove(); }新增英雄则在 hero-insert.component.ts 中通过db.hero.insert({ name, color, hp: 100 })完成name与color使用 Angular 的signal状态管理提交成功后清空输入框。该行为有端到端测试背书e2e.test.js 使用 TestCafe 驱动真实浏览器依次验证插入 BobKelso → 列表中可见 → 打开编辑弹窗 → 保存后英雄仍在 → 删除后列表回到空态的完整链路测试通过#hero-list、#no-heroes、.hero-list-item等 DOM 选择器断言每一步。多标签页协同Leader Election 与多实例浏览器端打开两个标签页时两个页面各自持有一个数据库实例都监听同一份 localstorage 存储。RxDB 通过 Leader Election 插件协调写入只有一个标签页会被选为领导者其余标签页的写入排队等待领导者处理避免并发写冲突。示例在 database.service.ts 中展示了领导者确认后的副作用——把标题加上皇冠符号if (!isNative) { db.waitForLeadership().then(() { console.log(isLeader now); document.title ♛ document.title; }); }因此在两个标签页同时打开应用两边实时同步更新这一特性正是multiInstance: true与 Leader Election 插件协同的结果而在原生端单 WebView两者都被显式关闭/跳过避免无谓开销。原生设备上的存储选型示例统一使用localstorage RxStoragegetRxStorageLocalstorage()理由是它不依赖任何原生插件、浏览器与 Capacitor WebView 内开箱即用。但 localstorage 有容量与性能上限database.service.ts 的注释给出了明确的升级路线当在原生设备上存储大量文档时建议切换到SQLite RxStorage它把数据存放在 WebView 之外由 Capacitor 原生层管理。更关键的是——换存储不是重写。getStorage()是唯一需要改动的点例如改为import { getRxStorageSQLite } from rxdb/plugins/storage-sqlite; // 原生端大数据量时启用 SQLite浏览器端仍可用 localstorage return getRxStorageSQLite();数据库的创建参数、集合定义、查询与响应式逻辑全部保持不变这正是 RxDB 存储层抽象的价值所在。关键文件索引示例说明examples/ionic/README.md依赖与脚本examples/ionic/package.jsonCapacitor 配置examples/ionic/capacitor.config.ts数据库创建与生命周期examples/ionic/src/app/services/database.service.ts类型化 Schemaexamples/ionic/src/app/schemas/hero.schema.ts类型组合examples/ionic/src/app/RxDB.d.ts响应式列表examples/ionic/src/app/components/heroes-list/heroes-list.component.ts应用初始化examples/ionic/src/main.ts端到端测试examples/ionic/test/e2e.test.js延伸阅读RxDB 本地存储方案对比rx-storage-localstorage 说明、rx-storage-sqlite 说明多标签页与领导者选举leader-election 文档响应式查询原理rx-query 文档开发模式校验插件dev-mode 文档写入钩子中间件middleware 文档快速上手quickstart 文档在 Ionic 中使用 RxDB 的深入讨论ionic-database 文章【免费下载链接】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),仅供参考
返回列表