前端的日常开发中,经常需要使用到localStorage存储,在使用Vue作为开发框架时,希望能与Vue的响应式系统集成到一起,可以像vue-router/vuex类似的模式使用。
效果
响应式设计
vue的响应式原理,是为一个对象添加特定的属性描述符,劫持它所有属性的getter/setter。
在这里,我们定义一个对象_storage
,遍历它的所有属性,通过官方暴露出的方法——Vue.util.defineReactive,把_storage
变成observable
了,那么只要修改_storage某个key
的值,依赖(模板/数据)就能实时更新。
接下来,我们需要把_storage
对象与本地的localStorage
做实时同步。粗糙的办法是每次更新_storage
时手动更新localStorage
,这就不符合我们做这个插件的初衷了,我们的愿景是自动同步。
我们也像Vue那样使用Object.defineProperty
的功能,在getter/setter
里调用对应的localStorage api
不就能解决问题了?可是这样的话_storage
的每一个属性都被Object.defineProperty
两次了,那么前面一次就会被覆盖,无法生效啊!
Vue.util.defineReactive
会保存前一次的getter/setter
(如果有的话),在未来每一次的getter/setter
中都会执行一次,在这里我们就可以调用相关的localStorage api
了。
Vue插件api设计
1. 初始化配置
配置项
- 定义所有在
localStorage
中使用到的key-value
,且需要指定数据类型,因为保存在本地存储的数据只能是字符串,所以取本地web存储数据时要根据原来的数据类型进行解析,例如对象需要JSON.parse
key
的公共前缀namespace
,方便标识某个项目使用的web存储
const storage = Vue.use(Storage, 'my-namespace', {
string: {
type: String,
default: 'test'
},
number: {
type: Number
},
object: {
type: Object,
default: {
hello: 'world'
}
}
})
class Storage {
static install (Vue, nameSpace, options) {
if (typeof nameSpace === 'object') {
options = nameSpace
nameSpace = 'vue-storage'
}
return new Storage(Vue, nameSpace, options)
}
}
插件定义Storage
类,且提供一个install
方法给Vue进行注册。key/value
提供type
和default
指定类型和默认值。
class Storage {
constructor (Vue, nameSpace, options = {}) {
const self = this
this.Vue = Vue
this.nameSpace = `${nameSpace}-`
this.options = options
// 刷新页面时,把本地storage重新取出来
const cacheStorage = Object.keys(window.localStorage)
.filter(key => new RegExp(`^${this.nameSpace}`).test(key))
.reduce((acc, key) => Object.assign(acc, {
[ key.replace(this.nameSpace, '') ]: window.localStorage[key]
}), {})
// 每种数据类型的默认值
const keyMap = [
[ String, '' ],
[ Boolean, '' ],
[ Number, '' ],
[ Array, [] ],
[ Object, {} ],
]
const map = this.typeMap = new Map(keyMap)
let _storage = this.storage = {
...(
Object.keys(options).reduce((acc, key) => {
const { type, default: val } = options[key]
if (!type) {
Vue.util.warn(`type of the field 'key' is required`)
return acc
}
return Object.assign(acc, {
[key]: val === undefined ? map.get(type) : val
})
}, {})
),
...cacheStorage
}
}
Storage
实例化的过程,先把本地web存储的数据提取出来,合并到配置的key/value
中,实现页面刷新不丢失数据。
考虑到数据没有定义默认值,通过keyMap
为每种数据类型定义一个默认值。最终得到一个_storage
对象,且赋值到this.storage
供外部实例调用。
2. 代理数据 getter/setter
class Storage {
constructor () {
Object.keys(_storage).forEach(key => {
try {
const val = _storage[key]
this.set(key, val)
} catch (e) {
Vue.util.warn('vue-storage-error', e)
}
// 把_storage中key对应的value取值代理到localStorage中去
Object.defineProperty(_storage, key, {
get: () => self.get(key),
set: (val) => self.set(key, val),
configurable: true
})
// 定义可观察对象
Vue.util.defineReactive(_storage, key, _storage[key])
})
}
get (key) {
let val = window.localStorage.getItem(this._getKey(key))
val = this._parse(key, val)
return val
}
set (key, val) {
try {
val = typeof val === 'object' ? JSON.stringify(val) : val
window.localStorage.setItem(this._getKey(key), val)
} catch (e) {
Vue.util.warn(`storage setting fail, please check the value`)
}
}
}
通过Object.defineProperty
,_storage
中key对应的value
的读取/写入实际是在localStorage
中去读取/写入。
3.暴露属性接口
class Storage {
constructor () {
const self = this
Object.defineProperty(Vue.prototype, '$storage', {
get: () => _storage
})
// 代理Storage实例
Object.defineProperty(Vue.prototype, '$storager', {
get: () => self
})
Vue.storage = _storage
Vue.storager = self
}
}
参照vue-router
那样,对Vue/Vue实例
暴露_storage
可观察数据和Storage
实例,实现在组件内可以通过this.$storage
获取_storage
。
4.提供插件公共方法
class Storage {
get (key) {
let val = window.localStorage.getItem(this._getKey(key))
val = this._parse(key, val)
return val
}
set (key, val) {
try {
val = typeof val === 'object' ? JSON.stringify(val) : val
window.localStorage.setItem(this._getKey(key), val)
} catch (e) {
Vue.util.warn(`storage setting fail, please check the value`)
}
}
remove (key) {
window.localStorage.removeItem(this._getKey(key))
}
clear () {
Object.keys(this.storage).forEach(key => {
const lsKey = this._getKey(key)
if (window.localStorage.hasOwnProperty(lsKey)) {
window.localStorage.removeItem(lsKey)
}
})
}
}
提供get/set/remove/clear
方法操作localStorage
。类似于getItem/setItem/removeItem/clear
。
完