如何構建通用存儲中間層
零、問題的由來
開門見山地說,這篇文章【又】是一篇安利軟文~,安利的對象就是
tuateam/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
)
實例方法 | tua-storage
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)
默認配置 | tua-storage
四、自動清理過期數據
默認在啟動時會進行一次過期數據清理(可以關閉),之後每過一段時間會再次清理。
什麼樣的數據會被清理呢?
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
sunnylqm/react-native-storage推薦閱讀: