vuex狀態(tài)持久化 方案一
前言
我們都知道Vuex
是一個(gè)狀態(tài)管理器,而他的缺點(diǎn)也很明確,在頁(yè)面刷新之后嫌术,Vuex
中的狀態(tài)都會(huì)被重置,這對(duì)于一些不想被重置的狀態(tài)數(shù)據(jù)而言牌借,
是一個(gè)不好的表現(xiàn)度气。如果是完全用Vue
構(gòu)建的 app 項(xiàng)目的話,則不需要考慮這些膨报,因?yàn)樵?app 中磷籍,不存在刷新瀏覽器的操作。
當(dāng)然现柠,如果是混合開發(fā)的院领,那還是有一些可能的,比如 app 端重新加載 webview
的話够吩,那也是等同于刷新瀏覽器的操作了比然,這個(gè)時(shí)候 Vuex
也會(huì)被重置。
而今天要討論的就是讓Vuex
的狀態(tài)持久化废恋,當(dāng)然谈秫,這只是其中一個(gè)方案,這里我們需要配合本地存儲(chǔ)來(lái)達(dá)到我們的目標(biāo)鱼鼓。
問(wèn)題
- 并不是所有狀態(tài)都需要存入本地緩存
- 重置默認(rèn)值拟烫,并不是所有的狀態(tài)默認(rèn)值都是
''
實(shí)現(xiàn)
Vuex Demo
首先我們初始化一個(gè)vue
項(xiàng)目:
vue init webpack vuexDemo
初始化成功后,我們通過(guò)yarn
安裝vuex
:
yarn add vuex
安裝成功后迄本,我們?cè)陧?xiàng)目根目錄src
建立一個(gè)store
文件夾硕淑,該文件夾用于存放vuex
的內(nèi)容,結(jié)構(gòu)入下:
store
├── state.js # vuex狀態(tài)集合
├── getter.js # state的派生狀態(tài) 可對(duì)state做些過(guò)濾或者其他操作
├── action.js # 異步mutation操作
├── mutation.js # 修改state狀態(tài)
├── mutation-type.js # mutation的類型
├── index.js # vuex主文件
我們對(duì)各個(gè)文件加入點(diǎn)簡(jiǎn)單的內(nèi)容:
// 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
})
這里之所以沒(méi)有引入getter
和action
嘉赎,一個(gè)是因?yàn)槲覀內(nèi)?code>state中的count
是直接取置媳,并沒(méi)有對(duì)其做什么特別的操作,所以getter
中就省略了公条。
而action
它提交的是一個(gè)mutation
拇囊,而且它和mutation
的區(qū)別在于:
-
mutation
是同步的,action
可以包含異步操作 -
mutation
直接修改state
靶橱,而action
提交的是mutation
寥袭,然后再讓mutation
去修改state
-
action
可以一次提交多個(gè)mutation
我們現(xiàn)在就是個(gè)簡(jiǎn)單的修改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>
這樣就完成了一個(gè)簡(jiǎn)單的vuex
例子了,當(dāng)點(diǎn)擊添加
按鈕的時(shí)候传黄,界面上的0
就會(huì)變成2
杰扫,并且如果裝有vue-devtools
的話,
也能在vuex
那一欄看到count
的數(shù)值也變成了2
膘掰,這里就不放動(dòng)圖演示了章姓。
持久化
接下來(lái)就是我們的關(guān)鍵內(nèi)容了,想要讓vuex
持久化识埋,自然離不開本地存儲(chǔ)localStorage
凡伊,我們往state
里加些內(nèi)容:
// state.js
const str = window.localStorage
const state = {
count: 0,
account: str.getItem('account') ? str.getItem('account') : ''
}
我們加入了一個(gè)account
屬性,這里表示如果緩存中有account
的話就從緩存中取惭聂,沒(méi)有則為空窗声。
然后我們也同樣設(shè)置下mutation
和mutation-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
我們?cè)诙xmutation-type
的時(shí)候,在尾部多加了個(gè)_0
用來(lái)表示辜纲,該字段是存入緩存中的笨觅。
但是我們不會(huì)選擇在mutation
中去做緩存操作,畢竟我個(gè)人認(rèn)為不適合在mutation
中做過(guò)多的邏輯操作耕腾,我們選擇將這部分邏輯操作放在action
中:
// action.js
import * as type from './mutation-type'
const str = window.localStorage
/**
* 緩存操作
*/
export const withCache = ({ commit }, { mutationType, data }) => {
commit(mutationType, data)
// 是不是以_0結(jié)尾 是的話表示需要緩存
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)
}
}
上面這段代碼解決幾個(gè)問(wèn)題:
- 因?yàn)樵?code>action中我不知道存儲(chǔ)的目標(biāo)屬于哪個(gè)
type
苍苞,所以將其當(dāng)做參數(shù)傳入 - 存入緩存的
key
我們?nèi)〉氖?code>SET_xxx中的xxx
(小寫),盡量保持和state
的字段名稱一致狼纬。比如SET_A
->a
羹呵,SET_B_0
->b
- 本地緩存存
Object
類型會(huì)變成[Object object]
,所以針對(duì)Object
類型的數(shù)據(jù)疗琉,我們需要將其轉(zhuǎn)成字符串再存入
提示
記得將action
在index.js
引入
然后我們?cè)?code>vue模板中冈欢,先引入mapActions
,然后進(jìn)行使用:
<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>
直接調(diào)用withCache
盈简,傳入mutationType
和data
凑耻,withCache
會(huì)根據(jù)mutationType
判斷哪些是需要存入緩存的,哪些是不需要的柠贤。
當(dāng)然香浩,不需要存入緩存的,也可以直接調(diào)用mapMutations
中的方法直接操作臼勉。
現(xiàn)在有個(gè)問(wèn)題邻吭,就是我緩存存入Object
類型的是字符串類型,所以我state
中的對(duì)應(yīng)數(shù)據(jù)也是字符串類型的宴霸,在模板中不利于使用镜盯,怎么辦岸裙?這時(shí)候就可以使用getter
了,我們?cè)?code>getter中將其轉(zhuǎn)成Object
類型即可:
// getter.js
export const getAccount = state => {
let account = state.account
if (typeof account === 'string' && !!account) return JSON.parse(account)
else return account
}
提示
上面的判斷還不夠完整速缆,應(yīng)該還要判斷是否是 JSON 字符串類型,是的話再進(jìn)行JSON.parse
操作恩闻,不然普通的字符串類型會(huì)報(bào)錯(cuò)艺糜,這個(gè)自行改善。
提示
記得將getter
在index.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>
到這基本就結(jié)束了幢尚,現(xiàn)在刷新瀏覽器破停,那些需要持久化的屬性就不會(huì)被重置了。
可是真的就結(jié)束了嗎尉剩?那么如果我要將緩存的數(shù)據(jù)給清空或者重置呢真慢?因?yàn)?code>state中每個(gè)屬性的默認(rèn)值都是不一樣的,可能為''
理茎、0
黑界、false
等各種類型的,那該怎么辦皂林?某問(wèn)題啦~
狀態(tài)重置
可以復(fù)制出一份state
作為它的初始默認(rèn)值朗鸠,比如在store
新建一個(gè)default_state.js
:
// default_state.js
const default_state = {
count: 0,
account: ''
}
export default default_state
然后我們定義一個(gè)類型為RESET_ALL_STATE
的mutation
:
// 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
最后我們?cè)?code>action中定義一個(gè)重置的操作resetAllState
:
// action.js
import * as type from './mutation-type'
import default_state from './default_state'
const str = window.localStorage
/**
* 重置所有狀態(tài)
*/
export const resetAllState = ({ commit }) => {
// 循環(huán)默認(rèn)state 設(shè)置初始值
Object.keys(default_state).forEach(state => {
commit(type.RESET_ALL_STATE, { state, value: default_state[state] })
})
// 將有緩存的數(shù)據(jù)清空
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)
}
}
/**
* 重置所有狀態(tài)
*/
export const resetAllState = ({ commit }) => {
// 循環(huán)默認(rèn)state 設(shè)置初始值
Object.keys(default_state).forEach(state => {
commit(type.RESET_ALL_STATE, { state, value: default_state[state] })
})
// 將有緩存的數(shù)據(jù)清空
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>
好了,現(xiàn)在是真的結(jié)束了础倍。
總結(jié)
可能代碼有些亂烛占,但是大致的思路我想應(yīng)該還是都能理解的。
如果還有有其他vuex
的持久化方式沟启,還會(huì)繼續(xù)更新的忆家。
也歡迎大家一同思考。