时隔一段时间,林大大又来更新了喔~
vue项目以单页面模式存在,那么必然会引起一个问题,就是会首次加载所有资源,即使对路由使用懒加载,也会比较慢,那么就有根据项目的功能模块来区分: 每个模块为一个vue项目,每次路由导航都是跳转到另一个项目的index.html中(蛮像以前的jq模式),那么会出现一种情况,就是每个vue项目都有自己的vuex数据存储,那么如何实现通用数据(用户信息及token等)和自有数据(每个模块自己的一些数据)呢? 那么我提出这么一种类:数据层持久化存储类,这种类
其核心是监听浏览器刷新的两个事件:beforeunload 和 unload ,当 beforeunload(浏览器刷新前事件监听)触发时,将当前项目的vuex存入localstorage中,unload(浏览器刷新时事件监听)触发时,将localstorage中属于当前项目的数据 通过以下方式合并入vuex中
this.$store.replaceState(
Object.assign(
{},
this.$store.state,
decorateState
)
)
decorateState 是对localstorage数据做筛选,只将属于当前项目的storage键值合并入vuex的state
/**
* 数据层持久化存储
* vuex 与 localStorage数据互转
* 示例: 在App.vue组件添加
* mounted () {
* this.dataWatch = new keepWatcher(this)
* this.dataWatch.add()
* },
* destroyed () {
* this.dataWatch.remove()
* }
*/
export default class keepWatcher {
constructor() {
if (!arguments.length) {
console.warn('components is require');
return
}
const [vm, whiteStore = []] = [].slice.call(arguments)
this.vmComp = vm // 组件
this.prefix = 'lddy_' // 通用前缀 项目整体名 我这里用我自己的名字的简写喔
this.suffix = window.location.pathname.replace(/[\/\-]/g, '_') // 各系统的标识符
this.whiteStore = whiteStore.concat(['user', 'permission']) // 只要前缀不要后缀的键值加入白名单内
this.notStore = ['tagsView'] // 不需要转换的键值 (例如tagsView数据量过大就不存入storage内)
this.listener = false // 销毁事件监听前判断是否初始化,一般不会触发,兼容开发环境优化脚手架触发bug
this.beforeWatch()
}
/**
* 添加事件监听
*/
add () {
// 刷新前事件监听
window.addEventListener("beforeunload", () => {
this.listener = true
for (let [key, val] of Object.entries(this.vmComp.$store.state)) {
if (!this.notStore.includes(key)) { // 首先筛掉过滤掉的键值
let flag = this.whiteStore.some(item => item.indexOf(key) !== -1)
if (flag) {
localStorage.setItem(this.prefix + key, JSON.stringify(val))
} else {
// 避免单个值数据过大导致存储失败问题
localStorage.setItem(this.prefix + key + this.suffix, JSON.stringify(val))
}
}
}
})
// 刷新时事件监听
window.addEventListener("unload", () => {
const asyncStorage = new Map()
const len = localStorage.length
for (let i = 0; i < len; i++) {
let curKey = localStorage.key(i)
let prefixIndex = curKey.indexOf(this.prefix)
if (prefixIndex !== -1) { // 筛选通用前缀lddy_
let suffixIndex = curKey.indexOf(this.suffix)
if (suffixIndex !== -1) { // 有后缀(标识每个系统自身的storage)
let nowKey = curKey.slice(prefixIndex + this.prefix.length, suffixIndex)
asyncStorage.set(nowKey, JSON.parse(localStorage.getItem(curKey)))
} else { // 无后缀(白名单storage 和 curSystem)
if (curKey !== this.prefix + 'curSystem') { // 白名单storage返回注入数据
let nowKey = curKey.slice(prefixIndex + this.prefix.length)
asyncStorage.set(nowKey, JSON.parse(localStorage.getItem(curKey)))
}
}
}
}
const decorateState = Array.from(asyncStorage.entries()).reduce((main, [key, value]) => ({...main, [key]: value}), {}) // Map转Object
this.vmComp.$store.replaceState(
Object.assign(
{},
this.vmComp.$store.state,
decorateState
)
)
})
}
/**
* 移除事件监听
*/
remove () {
this.listener && window.removeEventListener("beforeunload")
this.listener && window.removeEventListener("unload")
}
/**
* 判断切换系统没
* ? 进到其他系统内会将除白名单中的storage移除掉
* : 暂无操作
*/
beforeWatch () {
if (this.suffix !== (localStorage.getItem(this.prefix + 'curSystem') || '')) {
localStorage.setItem(this.prefix + 'curSystem', this.suffix)
const len = localStorage.length
for (let key in localStorage) {
if (key.indexOf(this.prefix) !== -1) {
let flagWhite = key === this.prefix + 'curSystem' ? false : true
this.whiteStore.map(item => {
if (key.indexOf(item) !== -1) {
flagWhite = false
}
})
let flagSpecial = key === this.prefix + 'curSystem' ? false : key.indexOf(this.suffix) === -1
console.log(flagWhite, flagSpecial, key);
if (flagWhite || flagSpecial) {
localStorage.removeItem(key)
}
}
}
} else {
// 同项目下刷新目前暂时不做修改
}
}
}
constructor() {
if (!arguments.length) {
console.warn('components is require');
return
}
const [vm, whiteStore = []] = [].slice.call(arguments)
this.vmComp = vm // 组件
this.prefix = 'lddy_' // 通用前缀lddy_
this.suffix = window.location.pathname.replace(/[\/\-]/g, '_') // 各系统的标识符
this.whiteStore = whiteStore.concat(['user', 'userinfo']) // 只要前缀不要后缀的键值加入白名单内
this.notStore = ['tagsView'] // 不需要转换的键值 (例如tagsView数据量过大就不存入storage内)
this.listener = false // 销毁事件监听前判断是否初始化,一般不会触发,兼容开发环境优化脚手架触发bug
this.beforeWatch()
}
arguments 参数,就是我们构造器的形参,是个类数组对象(和常规数组不一样),[].slice.call(arguments) 即将类数组对象转成数组,这部分在我另一篇博客有相关联,在这不详细解释:
有趣且重要的JS知识合集(13)call/apply/bind 源码级实现
调用
// 每个vue项目的app.vue文件
mounted () {
this.dataWatch = new keepWatcher(this)
this.dataWatch.add()
},
destroyed () {
this.dataWatch.remove()
}
this.vmComp = vm // 当前存储类运行在哪个组件中,是为了获取vuex中的state
this.prefix = 'lddy_' // 通用前缀 作为我们整个项目的标识符,用来区分其他不相关storage的
this.suffix = window.location.pathname.replace(/[\/\-]/g, '_') // 各系统的标识符
例如:
prefix :我们自己整个大项目是 'lddy_'
suffix : 然后有很多个小项目,比如 _home_index, _user_index, 这就是每个小项目(小系统)标识符,
那么这些标识符是从哪里取的呢,是从浏览器地址栏取得
http://49.233.69.175/home/index 首页的vue项目
http://49.233.69.175/user/index 用户中心的vue项目
我们取出时,我是用正则来替换 '/',所以标识符为 _home_index 和 _user_index
window.location.pathname 是地址栏路径,这个不多解释噢~
代表每个系统自己的数据就是 this.prefix + 每个系统自己的vuex中的state + this.suffix,如 lddy_system1_home_index, lddy_system1_user_index
this.whiteStore = whiteStore.concat(['token', 'userinfo']) // 白名单
例如:
白名单是啥意思呢?我们多项目中肯定有些东西是通用的,比如token啊权限啥的以及用户信息,那么这些东西
肯定不能加上特定系统的标识符,那么就是 this.prefix + 通用的vuex中的state,如 lddy_token, lddy_userinfo
this.notStore = ['tagsView'] // 不需要转换的键值 (例如tagsView数据量过大就不存入storage内)
例如:
我们知道vuex中为啥能存很多数据,是因为使用的是堆结构,互相引用,所以能存很多数据,那么这些数据存到localstorage,那就可能存不下,因为localstorage只能存5M,超出不会提示,就是不会存进去,那么这类数据我们将不会打上我们的前缀和后缀标识符
this.listener = false // 销毁事件监听前判断是否初始化,一般不会触发,兼容开发环境优化脚手架触发bug
this.beforeWatch()
在构造器初始化时调用,
作用是 判断切换系统没 ? 进到其他系统内会将除白名单中的storage移除掉 : 暂无操作
因为我们在每次实例化类时,会将当前的 window.location.pathname 作为我们的
this.suffix 后缀,会将他与一直存在localstorage中的 lddy_curSystem 进行
判断,不同的话则被认定为切换了系统(跳转到另一个vue项目),切换系统后,除
去白名单中的通用数据不会删除外,其他不属于当前系统标识符的数据都会被删除
this.add()
添加事件监听,里面有两个对浏览器进行刷新监听的事件 beforeunload 和 unload, 前者是将
vuex中state加上前缀标识(是白名单的话只加前缀,是系统自己的话,还要加上后缀),后者
是将localstorage数据返回注入到vuex的state中
关键点有两个:
1、Map转Object
const decorateState = Array.from(asyncStorage.entries()).reduce((main, [key, value]) => ({...main, [key]: value}), {})
2、 localstorage注入state替换
this.vmComp.$store.replaceState(
Object.assign(
{},
this.vmComp.$store.state,
decorateState
)
)
this.remove()
移除事件监听
在vue项目中注册了监听事件和定时器之类的,都要主动清除喔~
this.listener && window.removeEventListener("beforeunload")
this.listener && window.removeEventListener("unload")