Ionic 開發之 Ionic Storage 詳解
- 2019 年 11 月 6 日
- 筆記
Ionic Storage 是一款基於 localForage 用於 Ionic 應用程式的簡單 「鍵-值」 存儲模組,支援 SQLite 開箱即用。該工具可以根據平台自動選擇最佳的存儲引擎,而不用用戶關係具體的使用細節。模組記憶體儲引擎的默認選擇順序是 SQLite,IndexedDB,WebSQL 和 LocalStorage。
在原生應用程式環境中運行時,存儲方式會優先使用 SQLite 的原因,是因為它最穩定和最廣泛使用的文件數據之一,並且避免了諸如 localStorage 和 IndexedDB 之類的一些陷阱,比如在低磁碟空間的情況下會自動清理數據。在實際開發中,如果你想執行任意 SQL 查詢,你可以直接使用 Ionic Native SQLite 插件。
接下來,我們先來介紹一下 Ionic Storage 的安轉與使用。
安裝與使用
首先,如果你想使用 SQLite,請先安裝 cordova-sqlite-storage 插件:
$ ionic cordova plugin add cordova-sqlite-storage
接下來,安裝 @Ionic/storage:
$ npm install --save @ionic/storage
然後,導入 IonicStorageModule
並把它添加到根模組 NgModule 的 imports 列表中:
import { IonicStorageModule } from '@ionic/storage'; @NgModule({ declarations: [ // ... ], imports: [ BrowserModule, IonicModule.forRoot(MyApp), IonicStorageModule.forRoot() ], bootstrap: [IonicApp], entryComponents: [ // ... ], providers: [ // ... ] }) export class AppModule {}
之後,你就可以在頁面或組件中注入 Storage
服務:
import { Component } from '@angular/core'; import { NavController } from 'ionic-angular'; import { Storage } from '@ionic/storage'; @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { constructor( public navCtrl: NavController, public storage: Storage) { } }
為了確保進行數據操作時,存儲系統已經初始化完成。你可以在使用前調用 Storage.ready()
方法,不過該方法僅在 1.1.7 以上的版本才支援:
this.storage.ready().then((db) => { });
若需要保存數據,則可以調用 set(key, value)
方法:
this.storage.set('name', 'semlinker');
若想要獲取上面已存儲的 name
資訊,你可以調用 get(key)
方法:
this.storage.get('name').then((name) => { console.log('Me: Hey, ' + name + '! You have a very nice name.'); console.log('You: Thanks! I got it for my birthday.'); });
當然,要移除已存儲的項目,可以使用 remove(key)
方法:
this.storage.remove('name').then(() => { console.log('Name item has been removed'); });
下面我們繼續來介紹如何配置 IonicStorageModule 模組。
配置 Storage
你可以使用特定的存儲引擎優先順序配置存儲引擎,也可以將自定義配置項配置為 localForage。更多的選項,請參閱 localForage 配置文檔:https://github.com/localForage/localForage#configuration
注意:任何自定義配置將與默認配置合併
import { IonicStorageModule } from '@ionic/storage'; @NgModule({ declarations: [...], imports: [ IonicStorageModule.forRoot({ name: '__mydb', driverOrder: ['indexeddb', 'sqlite', 'websql'] }) ], bootstrap: [...], entryComponents: [...], providers: [...] }) export class AppModule { }
IonicStorageModule 源碼分析
IonicStorageModule 定義
import { NgModule, ModuleWithProviders } from '@angular/core'; import { getDefaultConfig, provideStorage, Storage, StorageConfig, StorageConfigToken } from './storage'; export { StorageConfig, StorageConfigToken, Storage }; @NgModule() export class IonicStorageModule { static forRoot(storageConfig: StorageConfig = null): ModuleWithProviders { return { ngModule: IonicStorageModule, providers: [ { provide: StorageConfigToken, useValue: storageConfig }, { provide: Storage, useFactory: provideStorage, deps: [StorageConfigToken] } ] }; } }
在前面的配置章節,我們通過調用 forRoot()
方法進行初始化操作:
IonicStorageModule.forRoot({ name: '__mydb', driverOrder: ['indexeddb', 'sqlite', 'websql'] })
storageConfig 對象除了包含 name 和 driverOrder 屬性外,還支援其它的屬性,StorageConfig 介面的定義如下:
export interface StorageConfig { name?: string; version?: number; size?: number; storeName?: string; description?: string; driverOrder?: string[]; // 存儲引擎的應用順序 dbKey?: string; }
在 IonicStorageModule 模組內,配置了兩個 provider:
providers: [ { provide: StorageConfigToken, useValue: storageConfig }, { provide: Storage, useFactory: provideStorage, deps: [StorageConfigToken] } ]
第一個 provider 使用 useValue 的方式進行註冊,這裡 StorageConfigToken 的定義如下:
export const StorageConfigToken = new InjectionToken<any>( 'STORAGE_CONFIG_TOKEN' );
第二個 provider 使用 useFactory 的方式進行註冊,對應的工廠函數 provideStorage 定義如下:
export function provideStorage(storageConfig: StorageConfig): Storage { const config = !!storageConfig ? storageConfig : getDefaultConfig(); return new Storage(config); }
provideStorage 函數內部,會先判斷 storageConfig 是否有效,如果無效的時候,會使用通過調用 getDefaultConfig() 方法獲取默認的配置:
export function getDefaultConfig() { return { name: '_ionicstorage', storeName: '_ionickv', dbKey: '_ionickey', driverOrder: ['sqlite', 'indexeddb', 'websql', 'localstorage'] }; }
下面我們來分析一下最核心的 Storage 類。
Storage 類
Storage 構造函數
import { Injectable, InjectionToken, Optional } from '@angular/core'; import LocalForage from 'localforage'; import CordovaSQLiteDriver from 'localforage-cordovasqlitedriver'; export class Storage { private _dbPromise: Promise<LocalForage>; private _driver: string = null; // constructor(config: StorageConfig) { this._dbPromise = new Promise((resolve, reject) => { let db: LocalForage; const defaultConfig = getDefaultConfig(); // 獲取默認配置 const actualConfig = Object.assign(defaultConfig, config || {}); // 合併配置 LocalForage.defineDriver(CordovaSQLiteDriver) .then(() => { db = LocalForage.createInstance(actualConfig); // 創建db實例 }) .then(() => db.setDriver(this._getDriverOrder(actualConfig.driverOrder)) // 設置資料庫驅動 ) .then(() => { this._driver = db.driver(); // 返回最終使用的驅動類型 resolve(db); }) .catch(reason => reject(reason)); }); } }
上面程式碼中,在調用 db.setDriver()
方法時,會調用內部的 _getDriverOrder()
方法轉換成相應的驅動:
private _getDriverOrder(driverOrder) { return driverOrder.map(driver => { switch (driver) { case 'sqlite': return CordovaSQLiteDriver._driver; case 'indexeddb': return LocalForage.INDEXEDDB; case 'websql': return LocalForage.WEBSQL; case 'localstorage': return LocalForage.LOCALSTORAGE; } }); } // https://github.com/localForage/localForage/blob/master/src/localforage.js const DefaultDrivers = { INDEXEDDB: idbDriver, WEBSQL: websqlDriver, LOCALSTORAGE: localstorageDriver };
Storage 成員方法
在繼續分析之前,我們先來大致瀏覽一下 Storage 類中定義的成員方法:
- driver() —— 返回 string 或 null,表示正在使用驅動的名稱;
- ready() —— 返回
Promise<LocalForage>
對象,當存儲初始化完成後會進入 resolved 狀態; - get(key) —— 獲取與給定鍵相關聯的值,返回 Promise 對象;
- set(key, value) —— 設置給定鍵的值,返回 Promise 對象;
- remove(key) —— 刪除與此鍵關聯的值,返回 Promise 對象;
- clear() —— 清除整個鍵值存儲,返回 Promise 對象;
- length() —— 獲取已存儲對象的個數,返回 Promise 對象;
- keys() —— 返回用存儲中的所有鍵,返回 Promise 對象;
- forEach(iteratorCallback) —— 迭代每個鍵值對,返回 Promise 對象:
- iteratorCallback —— (value, key, iterationNumber)
driver() 和 ready() 方法的實現很簡單:
get driver(): string | null { // 私有屬性 _driver getter 方法 return this._driver; } ready(): Promise<LocalForage> { return this._dbPromise; // _dbPromise: Promise<LocalForage>; }
下面來看一下我們常用的 get、set 和 remove 等方法:
// 獲取與給定鍵相關聯的值,返回 Promise 對象 get(key: string): Promise<any> { return this._dbPromise.then(db => db.getItem(key)); } // 設置給定鍵的值,返回 Promise 對象 set(key: string, value: any): Promise<any> { return this._dbPromise.then(db => db.setItem(key, value)); } // 刪除與此鍵關聯的值,返回 Promise 對象 remove(key: string): Promise<any> { return this._dbPromise.then(db => db.removeItem(key)); } // 清除整個鍵值存儲,返回 Promise 對象 clear(): Promise<void> { return this._dbPromise.then(db => db.clear()); }
最後我們來看一下剩下的三個方法:
// 獲取已存儲項的個數,返回 Promise 對象 length(): Promise<number> { return this._dbPromise.then(db => db.length()); } // 返回用存儲中的所有鍵,返回 Promise 對象 keys(): Promise<string[]> { return this._dbPromise.then(db => db.keys()); } // 迭代每個鍵值對,返回 Promise 對象 forEach( iteratorCallback: (value: any, key: string, iterationNumber: Number) => any ): Promise<void> { return this._dbPromise.then(db => db.iterate(iteratorCallback)); }
總結
其實分析完 IonicStorageModule 模組,我們會發現它的內部實現並不複雜。它只是對 localForage 的 API 進行簡單的封裝,實際的存儲功能還是交由 localForage 來完成,感興趣的小夥伴可以研究一下。實際的開發過程中,在數據存儲時,我們可能還會涉及數據響應式、數據加密、數據壓縮、數據遷移和備份,有上述需求的同學,可以了解一下 rxdb 這個庫。