如何構建通用儲存中間層
零、問題的由來
開門見山地說,這篇文章【又】是一篇安利軟文~,安利的物件就是 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)
複製程式碼
七、小結
還在為 web 端、小程式端、React-Native 端、node 端業務側程式碼使用不一樣的方式呼叫儲存層煩惱麼?還在為手動資料同步,儲存資料,處理過期邏輯而煩躁麼?各位開發者老爺們不妨試一試 tua-storage,(擠需體驗三番鍾,裡造會幹我一樣,愛象介款工具)。
靈感來源
inspired by react-native-storage