戰(zhàn):構(gòu)建帶 CouchDB 實(shí)時(shí)同步的英雄列表應(yīng)用)
數(shù)據(jù)庫NoSQL嵌入式數(shù)據(jù)庫實(shí)時(shí)數(shù)據(jù)庫【免費(fèi)下載鏈接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/項(xiàng)目地址https://gitcode.com/gh_mirrors/rx/rxdb點(diǎn)擊查看免費(fèi)下載本文以 RxDB 倉庫中的 Vue 示例 為藍(lán)本完整講解如何在 Vue 3 TypeScript 應(yīng)用中接入 RxDB包括本地?cái)?shù)據(jù)庫初始化、Schema 與類型定義、響應(yīng)式查詢、ORM 方法、插入/編輯/刪除流程以及通過 CouchDB 協(xié)議與pouchdb-server實(shí)現(xiàn)的實(shí)時(shí)雙向同步。讀完本文你將能獨(dú)立復(fù)刻一套本地優(yōu)先local-first、多端實(shí)時(shí)同步的 Vue 應(yīng)用骨架并理解其背后每個(gè)環(huán)節(jié)的源碼實(shí)現(xiàn)。示例項(xiàng)目概覽examples/vue是一個(gè) RxDB 官方倉庫中的最小可運(yùn)行示例它實(shí)現(xiàn)了一個(gè)英雄列表heroes-list應(yīng)用用戶可以輸入英雄的名字與顏色提交后英雄被寫入本地 RxDB 數(shù)據(jù)庫并在列表中實(shí)時(shí)渲染列表項(xiàng)支持編輯血量HP與刪除同時(shí)應(yīng)用會(huì)通過 CouchDB 協(xié)議把數(shù)據(jù)同步到本地pouchdb-server。整個(gè)項(xiàng)目還演示了多標(biāo)簽頁multitab下的 Leader Election 行為。項(xiàng)目核心目錄結(jié)構(gòu)相對倉庫根目錄examples/vue/src/database/index.ts數(shù)據(jù)庫創(chuàng)建、插件裝配、集合注冊、Hook 與同步邏輯examples/vue/src/schemas/Hero.schema.ts英雄集合的 RxJsonSchema 定義examples/vue/src/RxDB.d.ts為 Schema 字段與 ORM 方法補(bǔ)充的 TypeScript 類型examples/vue/src/components/HeroList、HeroInsert、HeroEdit 三個(gè)業(yè)務(wù)組件examples/vue/test/e2e.test.js基于 TestCafe 的端到端測試examples/vue/package.json腳本與依賴清單環(huán)境準(zhǔn)備與運(yùn)行步驟原文檔給出了從克隆倉庫到頁面打開的完整 7 步流程下面逐條展開并補(bǔ)充每步背后的作用克隆整個(gè) RxDB 倉庫git clone https://gitcode.com/gh_mirrors/rx/rxdb進(jìn)入項(xiàng)目根目錄并安裝依賴、構(gòu)建 RxDB 主庫cd rxdb npm install npm run build這一步會(huì)構(gòu)建出 RxDB 核心包的產(chǎn)物供示例通過本地打包文件引用。進(jìn)入示例目錄cd examples/vue執(zhí)行預(yù)安裝與安裝npm run preinstall npm installpreinstall腳本見 examples/vue/package.json執(zhí)行的是(cd ../../ npx yarn1.22.22 pack ../../ --filename ./examples/vue/rxdb-local.tgz)它把倉庫根目錄的 RxDB 源碼打包成本地rxdb-local.tgz隨后npm install通過依賴rxdb: file:rxdb-local.tgz安裝這份本地構(gòu)建產(chǎn)物。package.json 中有一行醒目的注釋說明了這一設(shè)計(jì)在 GitHub CI 中使用本地 RxDB 構(gòu)建產(chǎn)物而真實(shí)項(xiàng)目中應(yīng)當(dāng)直接從 npm 安裝rxdb。啟動(dòng)應(yīng)用npm startstart腳本通過concurrently同時(shí)拉起兩個(gè)進(jìn)程見 examples/vue/package.jsonserverpouchdb-server --host 0.0.0.0 -p 10101 -m -d /tmp/pouchdb-server/ -n true即在 10101 端口啟動(dòng)一個(gè)兼容 CouchDB 協(xié)議的本地同步服務(wù)端devvue-cli-service serve即 Vue CLI 開發(fā)服務(wù)器。瀏覽器打開應(yīng)用地址http://127.0.0.1:8080/重要不要使用localhost訪問。原因可以從源碼推斷數(shù)據(jù)庫初始化時(shí)同步地址由window.location.hostname動(dòng)態(tài)拼接而成見 examples/vue/src/database/index.tsconst syncURL http:// window.location.hostname :10101/;若用localhost打開頁面瀏覽器可能將其解析為 IPv6 的::1而pouchdb-server監(jiān)聽在0.0.0.0兩者可能導(dǎo)致同步請求無法連通使用127.0.0.1可確保前端與同步服務(wù)端之間的地址一致。數(shù)據(jù)庫初始化與插件裝配數(shù)據(jù)庫的全部初始化邏輯集中在 examples/vue/src/database/index.ts。它展示了 RxDB 典型的插件化裝配方式1. 按環(huán)境加載插件import { RxDBDevModePlugin } from rxdb/plugins/dev-mode; if (process.env.NODE_ENV development) { // in dev-mode we add the dev-mode plugin // which does many checks and adds full error messages addRxPlugin(RxDBDevModePlugin); } import { RxDBLeaderElectionPlugin } from rxdb/plugins/leader-election; addRxPlugin(RxDBLeaderElectionPlugin);RxDBDevModePlugin僅在開發(fā)環(huán)境加載它會(huì)在運(yùn)行時(shí)做大量 Schema、查詢與存儲(chǔ)一致性檢查并輸出完整的錯(cuò)誤消息幫助盡早發(fā)現(xiàn)問題參見 dev-mode 插件文檔RxDBLeaderElectionPlugin提供多標(biāo)簽頁/多實(shí)例下的 Leader 選舉能力示例用它來標(biāo)記當(dāng)前哪個(gè)標(biāo)簽頁是 Leaderdb.waitForLeadership().then(() { console.log(isLeader now); document.title ? document.title; });2. 創(chuàng)建數(shù)據(jù)庫與存儲(chǔ)引擎const db await createRxDatabaseRxHeroesCollections({ name: heroes, storage: wrappedValidateAjvStorage({ storage: getRxStorageLocalstorage() }) // password: myLongAndStupidPassword // no password needed });name: heroes是數(shù)據(jù)庫名存儲(chǔ)層使用了wrappedValidateAjvStorage包裹getRxStorageLocalstorage()外層負(fù)責(zé)在寫入前用 AJV 按 Schema 校驗(yàn)文檔內(nèi)層storage-localstorage把數(shù)據(jù)持久化到瀏覽器 LocalStorage參見 rx-storage-localstorage 與 rx-storage.mdpassword字段被注釋掉說明該示例未啟用加密需要加密時(shí)可參考 encryption.md。創(chuàng)建完成后示例還把db掛到window上便于在控制臺(tái)調(diào)試(window as any).db db;3. 注冊集合與 ORM 方法await db.addCollections({ heroes: { schema: heroSchema, methods: { hpPercent(this: RxHeroDocument): number { return (this.hp / this.maxHP) * 100; }, }, }, });methods為集合文檔注冊 ORM 方法hpPercent()計(jì)算當(dāng)前血量百分比之后任何hero文檔實(shí)例都能直接調(diào)用hero.hpPercent()見 HeroList.vue 中生命條寬度的計(jì)算。4. 寫入前 Hook顏色唯一性校驗(yàn)db.collections.heroes.preInsert((docObj: RxHeroDocumentType) { const color docObj.color; return db.collections.heroes .findOne({ selector: { color } }) .exec() .then((has: RxHeroDocument | null) { if (has ! null) { alert(another hero already has the color color); throw new Error(color already there); } return db; }); }, true);preInsertHook 在每次插入前查詢同色英雄若已存在則彈窗提示并拋出錯(cuò)誤阻止寫入。第二個(gè)參數(shù)true表示該 Hook 為異步返回 PromiseRxDB 會(huì)等待其完成后再執(zhí)行真正的寫入。5. 以 Vue 插件形式注入createDatabase()最終返回一個(gè)實(shí)現(xiàn)了 VuePlugin接口的對象return { install(app: any) { app.provide(KEY_DATABASE, db); }, };結(jié)合 main.tsconst database createDatabase(); const app createApp(App).use(router); database.then(db { app.use(db).mount(#app); });數(shù)據(jù)庫就緒后通過app.use(db)注入全局業(yè)務(wù)組件內(nèi)用useDatabase()內(nèi)部調(diào)用inject(KEY_DATABASE)即可拿到類型安全的數(shù)據(jù)庫實(shí)例——這是 Vue 3 Composition API 下管理 RxDB 單例的推薦模式。Schema 定義與 TypeScript 類型系統(tǒng)英雄集合的 RxJsonSchemaexamples/vue/src/schemas/Hero.schema.ts 定義了heroes集合的完整 Schemaconst schema: RxJsonSchemaRxHeroDocumentType { title: hero schema, description: describes a simple hero, version: 0, keyCompression: false, primaryKey: name, type: object, properties: { name: { type: string, default: , maxLength: 100 }, color: { type: string, default: }, maxHP: { type: number, minimum: 0, maximum: 1000 }, hp: { type: number, minimum: 0, maximum: 100, default: 100 }, team: { description: color of the team this hero belongs to, type: string }, skills: { type: array, maxItems: 5, uniqueItems: true, items: { type: object, properties: { name: { type: string }, damage: { type: number }, }, }, default: [], }, }, required: [color, hp, maxHP, skills], };關(guān)鍵字段說明字段約束含義primaryKeyname以name作為主鍵主鍵字段必須是字符串且唯一version0Schema 版本號升級 Schema 時(shí)需遞增并配合遷移邏輯參見 migration-schema.mdkeyCompressionfalse示例未開啟鍵壓縮生產(chǎn)環(huán)境可參考 key-compression.md 減小存儲(chǔ)體積maxHP0 ~ 1000最大血量數(shù)值范圍hp0 ~ 100默認(rèn)100當(dāng)前血量skills數(shù)組maxItems: 5、uniqueItems: true技能列表最多 5 項(xiàng)且不重復(fù)required四個(gè)必填字段缺任一字段時(shí)校驗(yàn)失敗自定義類型聲明examples/vue/src/RxDB.d.ts 為 Schema 字段與 ORM 方法補(bǔ)齊了強(qiáng)類型避免業(yè)務(wù)代碼里到處anyexport interface RxHeroDocumentType { name: string; color: string; maxHP: number; hp: number; team?: string; skills: Array{ name?: string; damage?: string }; } interface RxHeroDocMethods { hpPercent(): number; } export type RxHeroDocument RxDocumentRxHeroDocumentType, RxHeroDocMethods; export type RxHeroCollection RxCollectionRxHeroDocumentType, RxHeroDocMethods, {}; export interface RxHeroesCollections { heroes: RxHeroCollection; } export type RxHeroesDatabase RxDatabaseRxHeroesCollections;這樣useDatabase()返回的RxHeroesDatabase、集合查詢得到的RxHeroDocument[]都具備完整的字段與方法類型提示。Vue 組件中的響應(yīng)式查詢與文檔操作列表查詢把 RxQuery 可觀察對象橋接到 Vue refHeroList.vue 演示了 RxDB 最核心的響應(yīng)式能力——查詢結(jié)果隨數(shù)據(jù)庫變化自動(dòng)更新const sub database.heroes .find({ selector: {}, sort: [{ name: asc }] }) .$.pipe( tap(() { // debounce to simulate slow load setTimeout(() (loading.value false), 1000); }) ) .subscribe((result: RxHeroDocument[]) { heroes.value result; }); onUnmounted(() { if (sub) { sub.unsubscribe(); } });要點(diǎn)find({ selector: {}, sort: [{ name: asc }] })返回 RxQuery$.是它的可觀察對象任何文檔插入、更新、刪除都會(huì)推送最新結(jié)果數(shù)組參見 rx-query.md 與 reactivity.md通過tap延遲 1 秒關(guān)閉 loading 狀態(tài)模擬慢加載的視覺反饋組件卸載時(shí)必須unsubscribe()釋放訂閱避免內(nèi)存泄漏列表項(xiàng)展示hero.hpPercent()計(jì)算的生命條寬度刪除按鈕直接調(diào)用文檔方法hero.remove()。插入構(gòu)建文檔對象HeroInsert.vue 在表單提交時(shí)構(gòu)造文檔并調(diào)用insertconst obj { name: hero.value.name, color: hero.value.color, hp: 100, maxHP: randomInt(100, 1000), skills: [] }; await database.heroes.insert(obj);maxHP使用random-int包在 1001000 之間隨機(jī)生成skills默認(rèn)空數(shù)組正好滿足 Schema 的required約束。插入后的文檔會(huì)立即觸發(fā)前面HeroList的查詢訂閱。編輯沖突檢測與增量更新HeroEdit.vue 展示了多端同步場景下的沖突處理思路// 文檔被外部改變?nèi)缌硪粯?biāo)簽頁或同步端 firstValueFrom( props.hero.$.pipe( skip(1), map(() false) ) ).then((v: boolean) (synced.value v)); // 文檔被刪除 firstValueFrom(props.hero.deleted$).then(() (deleted.value true));訂閱hero.$文檔級可觀察對象并skip(1)跳過初始值一旦文檔被其他端修改就置synced false界面上彈出Someone else has changed this document警告訂閱hero.deleted$若文檔被他人刪除則禁止繼續(xù)保存保存時(shí)使用hero.incrementalPatch({ hp: props.hero.hp })做增量更新比全量覆蓋更安全、更高效參見 rx-document.md。頁面裝配Home.vue 用v-model:heroedit把待編輯英雄傳給HeroEdit并通過edit/cancel/submit事件協(xié)調(diào)三個(gè)組件的交互狀態(tài)。CouchDB 實(shí)時(shí)雙向同步示例的同步鏈路是瀏覽器 LocalStorageRxDB 存儲(chǔ)層→replicateCouchDB→pouchdb-server10101 端口。同步代碼位于 examples/vue/src/database/index.ts分兩步第一步為每個(gè)集合在 CouchDB 服務(wù)端創(chuàng)建同名數(shù)據(jù)庫PUT 請求await Promise.all( Object.values(db.collections).map(async (col) { try { await fetch(syncURL col.name /, { method: PUT }); } catch (err) { console.dir(err); } }) );第二步對每個(gè)集合啟動(dòng)replicateCouchDB實(shí)時(shí)復(fù)制const replicationState replicateCouchDB({ replicationIdentifier: my-vue-couch-replication, collection: col, url, live: true, pull: {}, push: {}, autoStart: true }); replicationState.error$.subscribe((err: any) { console.log(Got replication error:); console.dir(err); });參數(shù)說明replicationIdentifier復(fù)制狀態(tài)在本地持久化時(shí)的唯一標(biāo)識(shí)用于斷點(diǎn)續(xù)傳live: true開啟實(shí)時(shí)監(jiān)聽本地改動(dòng)推送到遠(yuǎn)端、遠(yuǎn)端改動(dòng)拉回本地pull/push分別控制拉取與推送的行為空對象表示默認(rèn)行為可在這里配置過濾函數(shù)實(shí)現(xiàn)部分同步參見 partial-sync.mdautoStart: true創(chuàng)建后立即開始復(fù)制replicationState.error$訂閱復(fù)制過程中的錯(cuò)誤流便于排查網(wǎng)絡(luò)或協(xié)議問題。更完整的復(fù)制協(xié)議與沖突處理機(jī)制可參見 replication-couchdb.md、replication.md 與 transactions-conflicts-revisions.md。本地存儲(chǔ)僅使用storage-localstorage時(shí)建議先閱讀 rx-storage-localstorage-meta-optimizer.md了解多實(shí)例同步對存儲(chǔ)實(shí)現(xiàn)的額外要求。端到端測試驗(yàn)證 CRUD、多標(biāo)簽頁與 Leader Electionexamples/vue/test/e2e.test.js 用 TestCafe 覆蓋了示例的三個(gè)關(guān)鍵行為測試 1插入 / 編輯 / 刪除英雄輸入BobKelso與顏色black并提交斷言列表中文本包含Kelso隨后點(diǎn)擊垃圾桶圖標(biāo)刪除斷言列表項(xiàng)數(shù)量歸零對應(yīng)npm test中的test:run腳本。測試 2多標(biāo)簽頁同步打開multitab.html?frames2在 iframe#frame_0插入英雄SteveIrwin切換到#frame_1斷言列表中出現(xiàn)該名字——驗(yàn)證同一個(gè)瀏覽器存儲(chǔ)下多個(gè)標(biāo)簽頁能實(shí)時(shí)看到彼此的寫入。測試 3Leader Election打開 6 個(gè) iframe斷言恰好有一個(gè)標(biāo)簽頁標(biāo)題帶?標(biāo)記Leader通過#removeId輸入框殺掉當(dāng)前 Leader 后其余標(biāo)簽頁中會(huì)重新產(chǎn)生一個(gè)新的 Leader且仍然只有一個(gè)。這正是RxDBLeaderElectionPlugin與waitForLeadership()行為的端到端驗(yàn)證參見 leader-election.md。腳本與依賴清單速覽examples/vue/package.json 中的腳本分工腳本命令用途preinstall打包倉庫根目錄 RxDB 為rxdb-local.tgz用本地構(gòu)建產(chǎn)物替代 npm 包startconcurrently npm run server npm run dev同時(shí)啟動(dòng)同步服務(wù)端與 Vue 開發(fā)服務(wù)器serverpouchdb-server --host 0.0.0.0 -p 10101 -m -d /tmp/pouchdb-server/ -n true啟動(dòng) CouchDB 兼容服務(wù)端devvue-cli-service serveVue CLI 開發(fā)服務(wù)器默認(rèn) 8080 端口buildvue-cli-service build生產(chǎn)構(gòu)建build:servews -p 8888 -d dist/用 local-web-server 在 8888 端口預(yù)覽構(gòu)建產(chǎn)物test并發(fā)啟動(dòng) server、build:serve 與 test:run一鍵跑端到端測試運(yùn)行時(shí)依賴包括vue3.5.42、rxjs7.8.2RxDB 的響應(yīng)式依賴、rxdb本地 tgz、pouchdb-server4.2.0、vue-router與random-int開發(fā)依賴則包含vue/cli-service、typescript、testcafe、async-test-util等。若要把這套架構(gòu)遷移到真實(shí)項(xiàng)目只需把rxdb: file:rxdb-local.tgz換成 npm 上的正式版本并將同步地址指向真實(shí)的 CouchDB/兼容服務(wù)端即可。贊分享數(shù)據(jù)庫NoSQL嵌入式數(shù)據(jù)庫實(shí)時(shí)數(shù)據(jù)庫【免費(fèi)下載鏈接】rxdbThe local-first database that runs on every JS runtime and replicates with your existing backend - no vendor, no lock-in - https://rxdb.info/項(xiàng)目地址https://gitcode.com/gh_mirrors/rx/rxdb點(diǎn)擊查看免費(fèi)下載相關(guān)推薦RxDB CouchDB 復(fù)制插件實(shí)戰(zhàn)用 Sync Engine 對接 CouchDB 的高效實(shí)時(shí)同步方案RxDB CouchDB 復(fù)制插件實(shí)戰(zhàn)用 Sync Engine 對接 CouchDB 的高效實(shí)時(shí)同步方案 replicateCouchDB 是 RxDB 官數(shù)據(jù)庫NoSQL嵌入式數(shù)據(jù)庫實(shí)時(shí)數(shù)據(jù)庫React Native Database 實(shí)戰(zhàn)用 RxDB 構(gòu)建離線優(yōu)先、實(shí)時(shí)同步的移動(dòng)應(yīng)用React Native Database 實(shí)戰(zhàn)用 RxDB 構(gòu)建離線優(yōu)先、實(shí)時(shí)同步的移動(dòng)應(yīng)用 RxDB 是一個(gè) local first 的 NoSQL 文檔數(shù)據(jù)庫NoSQL嵌入式數(shù)據(jù)庫實(shí)時(shí)數(shù)據(jù)庫RxDB實(shí)時(shí)數(shù)據(jù)同步構(gòu)建多用戶協(xié)作應(yīng)用的最佳實(shí)踐RxDB實(shí)時(shí)數(shù)據(jù)同步構(gòu)建多用戶協(xié)作應(yīng)用的最佳實(shí)踐 RxDB通過其強(qiáng)大的實(shí)時(shí)同步協(xié)議和沖突解決機(jī)制為構(gòu)建多用戶協(xié)作應(yīng)用提供了完整的解決方案。本文深入探討了Rx數(shù)據(jù)庫NoSQL嵌入式數(shù)據(jù)庫實(shí)時(shí)數(shù)據(jù)庫創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考