美文网首页
vuex状态持久化 方案一

vuex状态持久化 方案一

作者: 慕小牧 | 来源:发表于2018-07-19 16:30 被阅读351次

    vuex状态持久化 方案一

    前言

    我们都知道Vuex是一个状态管理器,而他的缺点也很明确,在页面刷新之后,Vuex中的状态都会被重置,这对于一些不想被重置的状态数据而言,
    是一个不好的表现。如果是完全用Vue构建的 app 项目的话,则不需要考虑这些,因为在 app 中,不存在刷新浏览器的操作。

    当然,如果是混合开发的,那还是有一些可能的,比如 app 端重新加载 webview 的话,那也是等同于刷新浏览器的操作了,这个时候 Vuex 也会被重置。

    而今天要讨论的就是让Vuex的状态持久化,当然,这只是其中一个方案,这里我们需要配合本地存储来达到我们的目标。

    问题

    • 并不是所有状态都需要存入本地缓存
    • 重置默认值,并不是所有的状态默认值都是''

    实现

    Vuex Demo

    首先我们初始化一个vue项目:

    vue init webpack vuexDemo
    

    初始化成功后,我们通过yarn安装vuex

    yarn add vuex
    

    安装成功后,我们在项目根目录src建立一个store文件夹,该文件夹用于存放vuex的内容,结构入下:

    store
    ├── state.js            # vuex状态集合
    ├── getter.js           # state的派生状态 可对state做些过滤或者其他操作
    ├── action.js           # 异步mutation操作
    ├── mutation.js         # 修改state状态
    ├── mutation-type.js    # mutation的类型
    ├── index.js            # vuex主文件
    

    我们对各个文件加入点简单的内容:

    // state.js
    const state = {
      count: 0
    }
    
    export default state
    
    // mutation-type.js
    export const SET_COUNT = 'SET_COUNT'
    
    // mutation.js
    import * as type from './mutation-type'
    
    const mutation = {
      [type.SET_COUNT](state, data) {
        state.count = data
      }
    }
    
    export default mutation
    
    // index.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    // import * as actions from './action'
    // import * as getters from './getter'
    import state from './state'
    import mutations from './mutation'
    
    Vue.use(Vuex)
    
    export default new Vuex.Store({
      actions,
      getters,
      state,
      mutations
    })
    

    这里之所以没有引入getteraction,一个是因为我们取state中的count是直接取,并没有对其做什么特别的操作,所以getter中就省略了。

    action它提交的是一个mutation,而且它和mutation的区别在于:

    • mutation是同步的,action可以包含异步操作
    • mutation直接修改state,而action提交的是mutation,然后再让mutation去修改state
    • action可以一次提交多个mutation

    我们现在就是个简单的修改state,所以就先不管action

    然后在vue模板中:

    <template>
      <div class="hello">
        <h1>{{ msg }}</h1>
        <h3>{{ count }}</h3>
        <button @click="addStore">添加</button>
      </div>
    </template>
    
    <script>
    import { mapState, mapMutations } from 'vuex'
    export default {
      name: 'HelloWorld',
      computed: {
        ...mapState(['count'])
      },
      data() {
        return {
          msg: 'Welcome to Your Vue.js App'
        }
      },
      methods: {
        addStore() {
          this.SET_COUNT(2)
        },
        ...mapMutations(['SET_COUNT'])
      }
    }
    </script>
    

    这样就完成了一个简单的vuex例子了,当点击添加按钮的时候,界面上的0就会变成2,并且如果装有vue-devtools的话,
    也能在vuex那一栏看到count的数值也变成了2,这里就不放动图演示了。

    持久化

    接下来就是我们的关键内容了,想要让vuex持久化,自然离不开本地存储localStorage,我们往state里加些内容:

    // state.js
    const str = window.localStorage
    
    const state = {
      count: 0,
      account: str.getItem('account') ? str.getItem('account') : ''
    }
    

    我们加入了一个account属性,这里表示如果缓存中有account的话就从缓存中取,没有则为空。

    然后我们也同样设置下mutationmutation-type

    // mutation-type.js
    export const SET_COUNT = 'SET_COUNT'
    
    export const SET_ACCOUNT_0 = 'SET_ACCOUNT_0'
    
    // mutation.js
    import * as type from './mutation-type'
    
    const mutation = {
      [type.SET_COUNT](state, data) {
        state.count = data
      },
      [type.SET_ACCOUNT_0](state, account) {
        state.account = account
      }
    }
    
    export default mutation
    

    我们在定义mutation-type的时候,在尾部多加了个_0用来表示,该字段是存入缓存中的。

    但是我们不会选择在mutation中去做缓存操作,毕竟我个人认为不适合在mutation中做过多的逻辑操作,我们选择将这部分逻辑操作放在action中:

    // action.js
    import * as type from './mutation-type'
    const str = window.localStorage
    
    /**
     * 缓存操作
     */
    export const withCache = ({ commit }, { mutationType, data }) => {
      commit(mutationType, data)
      // 是不是以_0结尾 是的话表示需要缓存
      if (~mutationType.indexOf('_0')) {
        setToStorage(mutationType, data)
      }
    }
    
    // 正则太烂。。。就先这么写着了
    const reg = /(SET_)(\w+)(_0)/
    function setToStorage(type, data) {
      let key = type.match(reg)[2].toLowerCase()
      if (typeof data === 'string') str.setItem(key, data)
      else {
        let formatData = JSON.stringify(data)
        str.setItem(key, formatData)
      }
    }
    

    上面这段代码解决几个问题:

    • 因为在action中我不知道存储的目标属于哪个type,所以将其当做参数传入
    • 存入缓存的key我们取的是SET_xxx中的xxx(小写),尽量保持和state的字段名称一致。比如SET_A->aSET_B_0->b
    • 本地缓存存Object类型会变成[Object object],所以针对Object类型的数据,我们需要将其转成字符串再存入

    提示
    记得将actionindex.js引入

    然后我们在vue模板中,先引入mapActions,然后进行使用:

    <script>
    import { mapState, mapMutations, mapActions } from 'vuex'
    export default {
      name: 'HelloWorld',
      computed: {
        ...mapState(['count'])
      },
      data() {
        return {
          msg: 'Welcome to Your Vue.js App'
        }
      },
      methods: {
        addStore() {
          let account = { user: 'Randy', age: 22 }
          this.withCache({ mutationType: 'SET_COUNT', data: 2 })
          this.withCache({ mutationType: 'SET_ACCOUNT_0', data: account })
        },
        ...mapMutations(['SET_COUNT']),
        ...mapActions(['withCache'])
      }
    }
    </script>
    

    直接调用withCache,传入mutationTypedatawithCache会根据mutationType判断哪些是需要存入缓存的,哪些是不需要的。

    当然,不需要存入缓存的,也可以直接调用mapMutations中的方法直接操作。

    现在有个问题,就是我缓存存入Object类型的是字符串类型,所以我state中的对应数据也是字符串类型的,在模板中不利于使用,怎么办?这时候就可以使用getter了,我们在getter中将其转成Object类型即可:

    // getter.js
    export const getAccount = state => {
      let account = state.account
      if (typeof account === 'string' && !!account) return JSON.parse(account)
      else return account
    }
    

    提示
    上面的判断还不够完整,应该还要判断是否是 JSON 字符串类型,是的话再进行JSON.parse操作,不然普通的字符串类型会报错,这个自行改善。

    提示
    记得将getterindex.js引入

    然后在模板中使用mapGetters引入:

    <template>
      <div>
        ...
        {{getAccount.user}}
        ...
      </div>
    </template>
    <script>
    import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
    export default {
      name: 'HelloWorld',
      computed: {
        ...mapState(['count']),
        ...mapGetters(['getAccount'])
      },
      data() {
        return {
          msg: 'Welcome to Your Vue.js App'
        }
      },
      methods: {
        addStore() {
          let account = { user: 'Randy', age: 22 }
          this.withCache({ mutationType: 'SET_COUNT', data: 2 })
          this.withCache({ mutationType: 'SET_ACCOUNT_0', data: account })
        },
        ...mapMutations(['SET_COUNT']),
        ...mapActions(['withCache'])
      }
    }
    </script>
    

    到这基本就结束了,现在刷新浏览器,那些需要持久化的属性就不会被重置了。

    可是真的就结束了吗?那么如果我要将缓存的数据给清空或者重置呢?因为state中每个属性的默认值都是不一样的,可能为''0false等各种类型的,那该怎么办?某问题啦~

    状态重置

    可以复制出一份state作为它的初始默认值,比如在store新建一个default_state.js

    // default_state.js
    const default_state = {
      count: 0,
      account: ''
    }
    
    export default default_state
    

    然后我们定义一个类型为RESET_ALL_STATEmutation

    // mutation-type.js
    export const RESET_ALL_STATE = 'RESET_ALL_STATE'
    
    // mutation.js
    import * as type from './mutation-type'
    
    const mutation = {
      [type.SET_COUNT](state, data) {
        state.count = data
      },
      [type.SET_ACCOUNT_0](state, account) {
        state.account = account
      },
      [type.RESET_ALL_STATE](state, data) {
        state[`${data.state}`] = data.value
      }
    }
    
    export default mutation
    

    最后我们在action中定义一个重置的操作resetAllState

    // action.js
    import * as type from './mutation-type'
    import default_state from './default_state'
    const str = window.localStorage
    
    /**
     * 重置所有状态
     */
    export const resetAllState = ({ commit }) => {
      // 循环默认state 设置初始值
      Object.keys(default_state).forEach(state => {
        commit(type.RESET_ALL_STATE, { state, value: default_state[state] })
      })
      // 将有缓存的数据清空
      Object.keys(type).forEach(typeItem => {
        if (~typeItem.indexOf('_0')) clearStorage(type[typeItem])
      })
    }
    
    const reg = /(SET_)(\w+)(_0)/
    function clearStorage(type) {
      let key = type.match(reg)[2].toLowerCase()
      str.removeItem(key)
    }
    

    完整action.js

    import * as type from './mutation-type'
    import default_state from './default_state'
    const str = window.localStorage
    
    /**
     * 缓存操作
     */
    export const withCache = ({ commit }, { mutationType, data }) => {
      commit(mutationType, data)
      if (~mutationType.indexOf('_0')) {
        // 需要缓存
        setToStorage(mutationType, data)
      }
    }
    
    /**
     * 重置所有状态
     */
    export const resetAllState = ({ commit }) => {
      // 循环默认state 设置初始值
      Object.keys(default_state).forEach(state => {
        commit(type.RESET_ALL_STATE, { state, value: default_state[state] })
      })
      // 将有缓存的数据清空
      Object.keys(type).forEach(typeItem => {
        if (~typeItem.indexOf('_0')) clearStorage(type[typeItem])
      })
    }
    
    const reg = /(SET_)(\w+)(_0)/
    function setToStorage(type, data) {
      let key = type.match(reg)[2].toLowerCase()
      if (typeof data === 'string') str.setItem(key, data)
      else {
        let formatData = JSON.stringify(data)
        str.setItem(key, formatData)
      }
    }
    
    function clearStorage(type) {
      let key = type.match(reg)[2].toLowerCase()
      str.removeItem(key)
    }
    
    

    vue模板中的完整使用:

    <template>
      <div class="hello">
        <h1>{{ msg }}</h1>
        <h3>{{ count }}</h3>
        <h4>{{ getAccount.user }}</h4>
        <button @click="addStore">添加</button>
        <button @click="clearStore">清空</button>
      </div>
    </template>
    
    <script>
    import { mapGetters, mapState, mapMutations, mapActions } from 'vuex'
    export default {
      name: 'HelloWorld',
      computed: {
        ...mapState(['count']),
        ...mapGetters(['getAccount'])
      },
      data () {
        return {
          msg: 'Welcome to Your Vue.js App'
        }
      },
      methods: {
        addStore () {
          let account = { user: 'Randy', age: 22 }
          this.withCache({ mutationType: 'SET_COUNT', data: this.msg })
          this.withCache({ mutationType: 'SET_ACCOUNT_0', data: account })
        },
        clearStore () {
          this.resetAllState()
        },
        ...mapMutations(['SET_COUNT']),
        ...mapActions(['withCache', 'resetAllState'])
      }
    }
    </script>
    

    好了,现在是真的结束了。

    总结
    可能代码有些乱,但是大致的思路我想应该还是都能理解的。
    如果还有有其他vuex的持久化方式,还会继续更新的。
    也欢迎大家一同思考。

    相关文章

      网友评论

          本文标题:vuex状态持久化 方案一

          本文链接:https://www.haomeiwen.com/subject/cpbtmftx.html