Vuex模塊化與持久化深入講解
概述
Vuex作為VUE狀態(tài)管理組件,能夠?qū)㈨?xiàng)目公共數(shù)據(jù)進(jìn)行統(tǒng)一管理。而且可以按照不同的業(yè)務(wù)功能將數(shù)據(jù)狀態(tài)分模塊管理。另外,對(duì)于網(wǎng)頁(yè)刷新導(dǎo)致Vuex狀態(tài)丟失的問(wèn)題可以使用vuex-persistedstate插件配置將數(shù)據(jù)保存在localStorage或者sessionStorage中。
本文測(cè)試環(huán)境如下:
“vue”: “^2.2.37”,
“vue-router”: “^3.0.1”,
“vuex”: “^3.0”,
“vuex-persistedstate”: “^4.1.0”
“secure-ls”: “^1.2.6”,
Vuex的模塊化
首先是main.js文件中引用Vuex組件,引入./store/index.js作為store參數(shù),用于實(shí)例化VUE對(duì)象。
// main.js
import Vue from 'vue'
import store from "./store";
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
其中./store/index.js文件是Vuex狀態(tài)實(shí)例,使用new Vuex.Store()進(jìn)行狀態(tài)實(shí)例化,并將根狀態(tài)的state,getters,mutations,actions添加到參數(shù),以及各個(gè)模塊添加到modules對(duì)象參數(shù)中。
import Vue from 'vue'
import Vuex from 'vuex'
import user from "./modules/user";
import room from "./modules/room"
// 使用Vuex組件
Vue.use(Vuex)
const state = () => ({})
const getters = {}
const mutations = {}
const actions = {}
// 實(shí)例化狀態(tài)對(duì)象
export default new Vuex.Store({
state, getters, mutations, actions,
modules: { // 將各個(gè)模塊放入modules屬性中
user, room, chat
}
})
以上./store/index.js文件中,有user,room,chat三個(gè)模塊,模塊之間大同小異,下面僅以user模塊進(jìn)行講解。其中
state數(shù)據(jù)狀態(tài)對(duì)象
state,作為數(shù)據(jù)狀態(tài)的存儲(chǔ),是一個(gè)匿名函數(shù)返回的對(duì)象,該對(duì)象中有一個(gè)curTheme主題字符串和一個(gè) curUser用戶(hù)對(duì)象。
// state: 用戶(hù)相關(guān)狀態(tài)
const state = () => ({
curTheme: 'light',
curUser: {
id: '123456',
name: '張三'
},
})getters計(jì)算屬性對(duì)象
getters,作為計(jì)算屬性,類(lèi)似vue組件中的computed屬性。該對(duì)象中是一個(gè)個(gè)的方法函數(shù),該函數(shù)按照順序有(state, getters, rootState, rootGetters)四個(gè)參數(shù)。前兩個(gè)參數(shù)state和getters是本模塊中的數(shù)據(jù)狀態(tài)對(duì)象和計(jì)算屬性對(duì)象,可在方法中按照如下格式進(jìn)行引用。
curUserId中可以使用state.curUser來(lái)訪(fǎng)問(wèn)當(dāng)前模塊中數(shù)據(jù)狀態(tài)對(duì)象中的curUser對(duì)象。
使用方法:state.curUserId
isCurUserId中返回的是一個(gè)函數(shù),該函數(shù)接收一個(gè)userId參數(shù),通過(guò)getters.curUserId可以訪(fǎng)問(wèn)當(dāng)前計(jì)算屬性對(duì)象中的curUserId屬性。
使用方法:state.isCurUserId(userId)
getCurThemeByUserId中返回的是一個(gè)函數(shù),該函數(shù)接收一個(gè)userId參數(shù),通過(guò)getters.isCurUserId(userId)函數(shù)確認(rèn)是否是當(dāng)前用戶(hù),并返回對(duì)應(yīng)主題。
使用方法:state.getCurThemeByUserId(userId)
const getters = {
// 獲取當(dāng)前用戶(hù)ID
curUserId: (state, getters, rootState, rootGetters) => {
return state.curUser ? state.curUser.id : undefined
},
// 比對(duì)userId是否是當(dāng)前用戶(hù)id
isCurUserId: (state, getters, rootState, rootGetters) => {
return (userId) => {
return userId == getters.curUserId;
}
},
// 根據(jù)userId獲取當(dāng)前主題
getCurThemeByUserId: (state, getters, rootState, rootGetters) => {
return (userId) => {
if(getters.isCurUserId(userId)) return state.curTheme;
else return '';
}
}
}
后兩個(gè)參數(shù)rootState和rootGetters可以用來(lái)訪(fǎng)問(wèn)根和其他模塊的數(shù)據(jù)狀態(tài)和計(jì)算屬性。比如下面是room模塊的getters屬性。
// room.js
const getters = {
// 測(cè)試
testRoom: (state, getters, rootState, rootGetters) => {
// 獲取userid
let curUserId = rootGetters['user/curUserId']
// 根據(jù)userId獲取當(dāng)前主題
let curTheme = rootGetters['user/getCurThemeByUserId'](curUserId)
return 'test';
}
}
actions異步請(qǐng)求對(duì)象
actions,內(nèi)部是一個(gè)個(gè)函數(shù),所有異步操作需要放到這里,且如果需要更改數(shù)據(jù)狀態(tài),則必須通過(guò)commit調(diào)用相應(yīng)的mutation。且需要注意該函數(shù)有兩個(gè)參數(shù)(context, payload),其中context是一個(gè)對(duì)象,包括了state,‘rootState’,‘commit’,‘dispatch’,‘getters’,'rootGetters’等參數(shù)。
需要注意的是,actions中的(context, payload)參數(shù)中context是對(duì)象,所以里面的參數(shù)可以是無(wú)須的。但是getters中的(state, getters, rootState, rootGetters) 是四個(gè)參數(shù),并且是有序的,千萬(wàn)注意順序!??!
// actions,異步操作,通過(guò)mutation進(jìn)行更新數(shù)據(jù)
const actions = {
//context:{
// state, 等同于store.$state,若在模塊中則為局部狀態(tài)
// rootState, 等同于store.$state,只存在模塊中
// commit, 等同于store.$commit
// dispatch, 等同于store.$dispatch
// getters 等同于store.$getters,若在模塊中為局部狀態(tài)
// rootGetters 等同于store.$getters
// }
// 用戶(hù)登錄
async login ({state, rootState, commit, dispatch, getters, rootGetters}, {name, passwd}) {
// getters使用
getters.curUserId // 獲取當(dāng)前模塊中的curUserId計(jì)算屬性
rootGetters['user/curUserId'] // 獲取user模塊中的curUserId計(jì)算屬性
// dispatch使用
dispatch('logout') // 調(diào)用本模塊中的logout異步請(qǐng)求
dispatch('room/getRoomByUserId', null, { root: true }) // 調(diào)用rooom模塊的getRoomByUserId異步請(qǐng)求
// commit使用
commit('SET_CUR_USER', null) // 調(diào)用本模塊mutation方法
commit('room/SET_ROOM_LIST', null, { root: true }) // 調(diào)用room模塊mutation方法
},
// 登出
async logout({commit}) {
let res = await $api.logout()
localStorage.removeItem("token");
commit("SET_CUR_USER", null);
// 關(guān)閉socket鏈接
websocket.close();
await $router.push("/")
},
mutations數(shù)據(jù)同步對(duì)象
mutations,數(shù)據(jù)同步對(duì)象,內(nèi)部是一個(gè)個(gè)同步函數(shù),該函數(shù)中主要是為了修改state屬性。注意千萬(wàn)不要在actions或者其他地方直接設(shè)置state數(shù)據(jù)狀態(tài),若要修改state狀態(tài),必須使用commit。因?yàn)橹挥性?code>mutations方法中修改才能觸發(fā)Vuex數(shù)據(jù)和視圖同步更新。
其他地方更新數(shù)據(jù),需要使用commit方法
commit('room/SET_ROOM_LIST', null)另外,對(duì)象和數(shù)組類(lèi)型修改時(shí)不能使用state.curUser = curUser這種方式。需要使用Vue.set()方法進(jìn)行修改,否則也不會(huì)觸發(fā)數(shù)據(jù)視圖的更新。
Vue.set(state, 'curUser', curUser) Vue.set(state.curUser, 'name', '張三') Vue.set(state.list, 0, "2");
// mutations,定義更新數(shù)據(jù)方法,同步操作
const mutations = {
SET_CUR_THEME (state, curTheme) {
state.curTheme = curTheme
},
SET_CUR_USER (state, curUser) {
Vue.set(state, 'curUser', curUser)
},
}
Vuex的使用方式
在自定義組件中使用
// RoomGaming.vue
import {mapActions, mapGetters, mapState, mapMutations} from "vuex";
export default {
computed: {
...mapState('user', ['curUser']),
...mapState('room', ['roomVO']),
...mapGetters('room', ['seatCount', 'playerList', 'curPlayer', 'curPlayerStatus',
'curPlayerCanAddSeat', 'curPlayerCanDelSeat', 'curPlayerIsOwner']),
},
methods: {
...mapActions('room', ['leaveRoom', 'playerReady', 'playerAddSeat', 'startGame']),
...mapMutations('room', [])
},
}
在自定義js文件中引用
// ReceiveService.js
import $store from '../store'
const testFunction = (data) => {
// mutations
$store.commit("gamexstx/SET_CLOCKWISE", data.clockwise);
$store.commit("gamexstx/SET_BOTTOM", data.bottom);
$store.commit("gamexstx/SET_DEGREE", data.degree);
$store.commit("gamexstx/SET_PLAYER_STATE", data.playerState);
// getters
let index = $store.getters['gamexstx/curDrawIndex']
let code = $store.getters['gamexstx/getCardInGroup1ByIndex'](index);
// actions
await $store.dispatch('cardxstx/playDrawCardAnim', {code, target});
// state
if($store.state.gamexstx.degree > 0) return;
}Vuex持久化配置
在main.js中添加plugins屬性,并設(shè)置key和storage屬性,key是鍵名,storage是存儲(chǔ)位置,可以是window.localStorage也可以是window.sessionStorage。
// main.js
import createPersistedState from 'vuex-persistedstate'
export default new Vuex.Store({
state,
getters,
mutations,
actions,
modules: {
user, room, chat, gamexstx, cardxstx
},
plugins: [
createPersistedState({
key: 'vuex',
storage: window.localStorage,
})
]
})因?yàn)閘ocalStorage不會(huì)隨著網(wǎng)頁(yè)刷新而丟失數(shù)據(jù),所以將Vuex數(shù)據(jù)狀態(tài)存儲(chǔ)在此解決刷新丟失數(shù)據(jù)的問(wèn)題。如下圖,可以看到相應(yīng)的數(shù)據(jù)存儲(chǔ)。

另外,由于是明文存儲(chǔ),可能存在安全問(wèn)題,可以使用以下插件對(duì)數(shù)據(jù)進(jìn)行加密存儲(chǔ)。
var ls = new SecureLS({
encodingType: "aes", //加密類(lèi)型
isCompression: false, //是否壓縮
encryptionSecret: "encryption", //PBKDF2值 加密秘密
});
export default new Vuex.Store({
state,
getters,
mutations,
actions,
modules: {
user, room, chat, gamexstx, cardxstx
},
plugins: [
createPersistedState({
// 以下使用ls加密
key: 'vuex',
storage: {
getItem: (key) => ls.get(key),
setItem: (key, value) => ls.set(key, value),
removeItem: (key) => ls.remove(key),
}
})
]
})
加密之后,控制臺(tái)顯示如下,可以看到vuex中內(nèi)容已加密。

main.js代碼
import Vue from 'vue'
import Vuex from 'vuex'
import user from "./modules/user";
import room from "./modules/room"
import chat from "./modules/chat"
import cardxstx from "./modules/cardxstx"
import gamexstx from "./modules/gamexstx";
import createPersistedState from 'vuex-persistedstate'
import SecureLS from "secure-ls";
import SystemConfig from "../consts/SystemConfig";
Vue.use(Vuex)
const state = () => ({})
const getters = {}
const mutations = {}
const actions = {}
var ls = new SecureLS({
encodingType: "aes", //加密類(lèi)型
isCompression: false, //是否壓縮
encryptionSecret: "encryption", //PBKDF2值 加密秘密
});
localStorage.removeItem(SystemConfig.storageKey);
export default new Vuex.Store({
state,
getters,
mutations,
actions,
modules: {
user, room, chat, gamexstx, cardxstx
},
plugins: [
createPersistedState({
key: SystemConfig.storageKey,
storage: window.localStorage,
// 以下使用ls加密
// key: SystemConfig.storageKey,
// storage: {
// getItem: (key) => ls.get(key),
// setItem: (key, value) => ls.set(key, value),
// removeItem: (key) => ls.remove(key),
// }
})
]
})modules/user.js代碼
// ./store/modules/user.js
import Vue from 'vue'
import $api from '../../api/inter'
import $router from "../../router";
import {Message} from "element-ui";
import websocket from "../../api/websocket";
import SystemConfig from "../../consts/SystemConfig";
// state: 用戶(hù)相關(guān)狀態(tài)
const state = () => ({
curTheme: 'light',
curUser: undefined,
})
// getters: 用戶(hù)相關(guān)計(jì)算屬性,類(lèi)似vue組件中的computed
const getters = {
//
curUserId: (state, getters, rootState, rootGetters) => {
return state.curUser ? state.curUser.id : undefined
},
getUserNameById: (state, getters, rootState, rootGetters) => {
return (userId) => {
if(state.curUser.id == userId) return state.curUser.name;
else return "無(wú)名氏"
}
}
}
// actions,異步操作,通過(guò)mutation進(jìn)行更新數(shù)據(jù)
const actions = {
//context:{
// state, 等同于store.$state,若在模塊中則為局部狀態(tài)
// rootState, 等同于store.$state,只存在模塊中
// commit, 等同于store.$commit
// dispatch, 等同于store.$dispatch
// getters 等同于store.$getters
// }
// 獲取用戶(hù)信息
async getCurUser ({ state, commit }) {
// dispatch('room/getRoomByUserId', value, { root: true }) // 調(diào)用另外模塊
// 獲取登錄后存儲(chǔ)在localStorage中的token值
let token = localStorage.getItem("token");
// console.log("token=" + token)
// 如果token為空則返回空
if(token == undefined || token == null) {
commit('SET_CUR_USER', null);
// Message.warning("用戶(hù)token失效,將移除本地token");
// 移除token
localStorage.removeItem("token")
// 關(guān)閉socket鏈接
websocket.close();
return;
}
try{
// 將token傳到后臺(tái)獲取對(duì)應(yīng)用戶(hù)信息
let res = await $api.getUserInfoByToken();
if(200 != res.code) throw new Error(res.message);
localStorage.removeItem(SystemConfig.storageKey); // 登陸成功后清空會(huì)話(huà)緩存
// 獲取到用戶(hù)信息,設(shè)置到curUser狀態(tài)中
commit('SET_CUR_USER', res.data);
// 設(shè)置websocket
websocket.connect(state.curUser.id)
} catch(err) {
// 會(huì)話(huà)失效后應(yīng)該清理本地緩存
localStorage.removeItem("token");
// 關(guān)閉socket鏈接
websocket.close();
return Promise.reject(err);
}
},
// 游客登錄
async loginAsNameless({ dispatch }) {
try{
let res = await $api.loginAsNameless()
await dispatch('loginSuccess', res.data);
} catch (err) {
Message.error(err);
return Promise.reject(err)
}
},
// 用戶(hù)登錄
async loginByUser({dispatch}, params) {
try{
let res = await $api.login(params)
// console.log("用戶(hù)登錄返回:", res)
await dispatch('loginSuccess', res.data);
}catch (err) {
Message.error(err)
return Promise.reject(err)
}
},
// 登錄之后的操作
async loginSuccess({state, dispatch}, token) {
// 設(shè)置本地緩存
localStorage.token = token;
// 獲取用戶(hù)信息
await dispatch('getCurUser');
// 如果獲取用戶(hù)信息成功,則打開(kāi)websocket并進(jìn)入大廳
if(state.curUser != null) {
await $router.push({path: '/hall'})
} else {
$router.push({path: "/"})
}
},
// 登出
async logout({commit}) {
let res = await $api.logout()
localStorage.removeItem("token");
commit("SET_CUR_USER", null);
// 關(guān)閉socket鏈接
websocket.close();
await $router.push("/")
},
// 修改密碼
async modifyPass({commit}, params) {
try{
await $api.modifyPass(params)
} catch (err) {
Message.error(err)
return Promise.reject(err)
}
}
}
// mutations,定義更新數(shù)據(jù)方法,同步操作
//
const mutations = {
SET_CUR_USER (state, curUser) {
Vue.set(state, 'curUser', curUser)
},
}
export default {
namespaced: true,
state,
getters,
mutations,
actions
}項(xiàng)目傳送門(mén):https://github.com/louislee92/vue-module-persistedstate
到此這篇關(guān)于Vuex模塊化與持久化深入講解的文章就介紹到這了,更多相關(guān)Vuex模塊化與持久化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS 實(shí)現(xiàn)獲取對(duì)象屬性個(gè)數(shù)的方法小結(jié)
這篇文章主要介紹了JS 實(shí)現(xiàn)獲取對(duì)象屬性個(gè)數(shù)的方法,結(jié)合實(shí)例形式總結(jié)分析了JS 獲取對(duì)象屬性個(gè)數(shù)的三種常用方法,需要的朋友可以參考下2023-05-05
VUE 配置vue-devtools調(diào)試工具及安裝方法
vue-devtools是一款基于chrome瀏覽器的插件,用于vue應(yīng)用的調(diào)試,這款vue調(diào)試神器可以極大地提高我們的調(diào)試效率。幫助我們快速的調(diào)試開(kāi)發(fā)vue應(yīng)用。這篇文章主要介紹了VUE 配置vue-devtools調(diào)試工具及安裝步驟 ,需要的朋友可以參考下2018-09-09
Vue props 單向數(shù)據(jù)流的實(shí)現(xiàn)
這篇文章主要介紹了Vue props 單向數(shù)據(jù)流的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11

