vue学习(4)多个vue项目的localstorage数据存储

时隔一段时间,林大大又来更新了喔~

1、为什么要用vuex持久化存储?

vue项目以单页面模式存在,那么必然会引起一个问题,就是会首次加载所有资源,即使对路由使用懒加载,也会比较慢,那么就有根据项目的功能模块来区分: 每个模块为一个vue项目,每次路由导航都是跳转到另一个项目的index.html中(蛮像以前的jq模式),那么会出现一种情况,就是每个vue项目都有自己的vuex数据存储,那么如何实现通用数据(用户信息及token等)和自有数据(每个模块自己的一些数据)呢? 那么我提出这么一种类:数据层持久化存储类,这种类

2、持久化存储类详解

2.1、首先是用来解决在浏览器刷新时,vuex数据丢失的问题

其核心是监听浏览器刷新的两个事件:beforeunload 和 unload ,当 beforeunload(浏览器刷新前事件监听)触发时,将当前项目的vuex存入localstorage中,unload(浏览器刷新时事件监听)触发时,将localstorage中属于当前项目的数据 通过以下方式合并入vuex中

this.$store.replaceState(
        Object.assign(
          {},
          this.$store.state,
          decorateState
        )
      )

decorateState 是对localstorage数据做筛选,只将属于当前项目的storage键值合并入vuex的state

2.2 、整体代码

/**
 * 数据层持久化存储
 * 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 {
      // 同项目下刷新目前暂时不做修改
    }
  }
}

2.3、类的属性及方法解析 

构造器及调用方式讲解

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")

你可能感兴趣的:(vue学习,vue.js,前端,javascript)