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的持久化方式,还会继续更新的。

也欢迎大家一同思考。

上次更新: 8/31/2018, 5:52:20 PM