如何構建通用儲存中間層
開門見山地說,這篇文章【又】是一篇安利軟文~,安利的物件就是ofollow,noindex">tua-storage 。
顧名思義,這就是一款儲存資料的工具。
用 tua-storage 好處大大的有麼?
那必須滴~,下面開始我的表演~
- 多端統一 api
- 支援資料同步
- 資料過期邏輯
- 自動清理過期資料
- 支援永久儲存
- 支援批量操作
一、多端統一 api
日常開發中,在不同的平臺下由於有不同的儲存層介面,所以往往導致相同邏輯的同一份程式碼要寫幾份兒。
例如,小程式中儲存資料要使用【非同步】的wx.setStorage
、wx.getStorage
或對應的同步方法;
而在 web 端使用 localStorage 的話,則是【同步】的setItem
、getItem
等方法;
在 React-Native 的場景下,使用的又是AsyncStorage
中【非同步】的setItem
、getItem
...
1.1.非同步方法
然而,經過tua-storage
的二次封裝,以上兩個方法統一變成了:
save load
此外還有一些其他方法:
-
clear
: 非同步清除(刪除多個) -
remove
: 非同步刪除(刪除單個) -
getInfo
: 非同步獲取資訊(如keys
)
1.2.同步方法
在某些場景下正好需要呼叫同步方法的話,咋辦咧?
與 Node.js 的 api 風格差不多,在上述非同步方法後面加上Sync
就是對應的同步方法:
saveSync loadSync clearSync removeSync getInfoSync
那麼在AsyncStorage
的場景下,壓根就沒有同步方法時呼叫以上方法會怎麼樣呢?
嗯,你猜得沒錯,會直接報錯...
1.3.區分場景
如何區分不同的場景呢?
在初始化的時候傳遞storageEngine
即可:
import TuaStorage from 'tua-storage' const tuaStorage = new TuaStorage({ // 小程式 storageEngine: wx, // web storageEngine: localStorage, // React-Native storageEngine: AsyncStorage, // Node.js storageEngine: {}, }) 複製程式碼
注意:傳遞的是【物件】,而非字串!
二、支援資料同步
對於一個二次封裝多端儲存層的庫來說,保證多端 api 的統一僅僅是常規操作而已。
tua-storage
的另一大亮點就是資料同步功能。
想想平時我們是怎麼使用儲存層的
- 讀取一個數據
-
正好儲存層裡有這個資料
- 返回資料(皆大歡喜,happy ending~)
-
假如儲存層裡沒這個資料
- 手動呼叫各種方法去同步這個資料
- 手動存到儲存層中,以便下次讀取
各位有沒有看出其中麻煩的地方在哪兒?
資料同步部分的複雜度全留給了業務側。
讓我們迴歸這件事的【初心 】:我僅僅需要獲取這個資料!我不管它是來自儲存層、來自介面資料、還是來自其他什麼地方...
2.1.資料同步函式
因此tua-storage
在讀取資料時很貼心地提供了一個syncFn
引數,作為資料同步的函式,當請求的資料不存在或已過期時自動呼叫該函式。並且資料同步後預設會儲存下來,這樣下次再請求時儲存層中就有資料了。
syncParams
的使用場景是介面需要傳參時,這些引數會傳給syncFn
。
tuaStorage.load({ key: 'some data', syncFn: ({ a }) => axios('some api url' + a), // 以下引數會傳到 syncFn 中 syncParams: { a: 'a' }, }) 複製程式碼
這麼一來,儲存層就和介面層對接起來了。業務側再也不用手動呼叫 api 獲取資料。
2.2.合併分散配置
每次讀取資料時如果都要手動傳同步函式,實際編碼時還是很麻煩...
不急,吃口藥~
tua-storage
在初始化時能夠傳遞一個叫做syncFnMap
引數。顧名思義,這是一個將key
和syncFn
對映起來的物件。
const tuaStorage = new TuaStorage({ // ... syncFnMap: { 'data one': () => axios('data one api'), 'data two': () => axios('data two api'), // ... }, }) // 不用手動傳 syncFn,預設匹配 syncFnMap 中的對應函式 tuaStorage.load({ key: 'data one' }) 複製程式碼
2.3.自動生成配置
其實手動編寫每個 api 請求函式也是很繁瑣的,要是有個根據配置自動生成請求函式的庫就好了~
誒~,巧了麼不是~。各位開發者老爺們瞭解一下同樣跨平臺的tua-api ~?
tua-storage
搭配tua-api
之後會變成這樣
import TuaStorage from 'tua-storage' import { getSyncFnMapByApis } from 'tua-api' // 本地寫好的各種介面配置 import * as apis from '@/apis' const tuaStorage = new TuaStorage({ syncFnMap: getSyncFnMapByApis(apis), }) 複製程式碼
三、資料過期邏輯
一般各個平臺的儲存層都沒有資料過期這一邏輯。但在使用tua-storage
時預設每個資料都有過期時間這一屬性。
3.1.預設過期時間
預設為 30 秒,可以在初始化時配置預設超時時間。
import TuaStorage from 'tua-storage' const tuaStorage = new TuaStorage({ // 改為 60 秒 defaultExpires: 60, }) // 返回一個 Promise tuaStorage .save({ key: 'data key', data: { foo: 'bar' }, // 這裡傳遞的過期時間優先順序更高 expires: 90, }) .then(console.log) .catch(console.error) // 儲存到 storage 中的資料大概長這樣 // key 之前會加上初始化傳入的預設字首 { 'TUA_STORAGE_PREFIX: data key': { expires: 90, rawData: { foo: 'bar' }, }, } 複製程式碼
3.2.資料儲存字首
為了保證存在 storage 中的資料名稱不衝突,以及實現版本控制,tua-storage
預設有一個儲存字首:storageKeyPrefix
。
預設值為TUA_STORAGE_PREFIX:
,所以在上一小節中儲存的資料會有一個奇怪的字首。
保證名稱不衝突很好理解,如何實現版本控制呢?
3.3.白名單機制
clear
函式能夠接受一個白名單陣列(因為內部是通過indexOf
來判斷的,所以不必填寫完整的key
值)。
import TuaStorage from 'tua-storage' const tuaStorage = new TuaStorage({ ... }) tuaStorage.clear(['key']) .then(console.log) .catch(console.error) // 假設現在 storage 中有以下資料 { 'foo': {}, 'bar': {}, 'foo-key': {}, 'bar-key': {}, } // 清除後剩下的資料是 { 'foo-key': {}, 'bar-key': {}, } 複製程式碼
所以在呼叫clear
時,在白名單中傳入新的儲存字首,即可實現刪除上一版本資料的功能。
import TuaStorage from 'tua-storage' // 上一版本的字首 const prefix1 = 'STORAGE_PREFIX_V1.0: ' // 這一版本的字首 const prefix2 = 'STORAGE_PREFIX_V1.1: ' const tuaStorage = new TuaStorage({ // 將預設字首切換成新版本的 storageKeyPrefix: prefix2, }) // 開始清除上個版本的資料 tuaStorage.clear([ prefix2 ]) .then(console.log) .catch(console.error) 複製程式碼
四、自動清理過期資料
預設在啟動時會進行一次過期資料清理(可以關閉),之後每過一段時間會再次清理。
什麼樣的資料會被清理呢?
4.1.清理邏輯
首先當然是清理已到過期時間的資料,即有一個屬性為expires
的資料,且當前時間已超過了該時間。
一旦遇到不滿足格式的資料(非物件、沒有expires
屬性)則跳過,這樣就不會誤清除其他程式儲存的資料。
4.2.清理時間間隔
在初始化時可傳入autoClearTime
修改預設自動清理時間間隔。
預設為一分鐘,注意是以秒為單位。
五、支援永久儲存
在某些場景下,可能不方便寫過期時間,這時預設可以傳遞expires: null
,標記該資料永不過期。
不喜歡用null
標記?
大丈夫~,初始化時傳遞neverExpireMark
即可修改為你喜歡的別的標記。
import TuaStorage from 'tua-storage' const tuaStorage = new TuaStorage({ neverExpireMark: 'never', }) // 永不過期 tuaStorage.save({ key: 'some key', data: 'some data', expires: 'never', }) 複製程式碼
六、支援批量操作
假設現在有一組資料需要儲存或讀取,常規操作就是使用Promise.all
發起多個操作。
import TuaStorage from 'tua-storage' const tuaStorage = new TuaStorage({ ... }) const dataToBeSaved = [ { key: 'key one', data: 'some data' }, { key: 'key two', data: 'some data' }, ] // 非同步 const result = dataToBeSaved .map(tuaStorage.save.bind(tuaStorage)) .then(Promise.all.bind(Promise)) // 同步 const result = dataToBeSaved .map(tuaStorage.saveSync.bind(tuaStorage)) 複製程式碼
講道理這樣寫還是挺煩的...所以tua-storage
的各個 api 還支援直接傳入陣列:
// 非同步 tuaStorage.save(dataToBeSaved) .then(console.log) .catch(console.log) // 同步 tuaStorage.saveSync(dataToBeSaved) 複製程式碼