零, 问题的由来
开门见山地说, 这篇文章[又] 是一篇安利软文~, 安利的对象就是 https://github.com/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)
详情参阅这里的文档
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 请求函数也是很繁琐的, 要是有个根据配置自动生成请求函数的库就好了~
诶~, 巧了么不是~. 各位开发者老爷们了解一下同样跨平台的 https://tuateam.github.io/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 端业务侧代码使用不一样的方式调用存储层烦恼么? 还在为手动数据同步, 保存数据, 处理过期逻辑而烦躁么? 各位开发者老爷们不妨试一试 https://github.com/tuateam/tua-storage ,(挤需体验三番钟, 里造会干我一样, 爱象介款工具).
灵感来源
inspired by https://github.com/sunnylqm/react-native-storage
来源: https://juejin.im/post/5bf40c48f265da61380ed6d8