Vuex模塊化與持久化深入講解
概述
Vuex作為VUE狀態(tài)管理組件,能夠?qū)㈨?xiàng)目公共數(shù)據(jù)進(jìn)行統(tǒng)一管理。而且可以按照不同的業(yè)務(wù)功能將數(shù)據(jù)狀態(tài)分模塊管理。另外,對于網(wǎng)頁刷新導(dǎo)致Vuex狀態(tài)丟失的問題可以使用vuex-persistedstate
插件配置將數(shù)據(jù)保存在localStorage
或者sessionStorage
中。
本文測試環(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對象。
// 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ù),以及各個模塊添加到modules
對象參數(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)對象 export default new Vuex.Store({ state, getters, mutations, actions, modules: { // 將各個模塊放入modules屬性中 user, room, chat } })
以上./store/index.js
文件中,有user
,room
,chat
三個模塊,模塊之間大同小異,下面僅以user
模塊進(jìn)行講解。其中
state數(shù)據(jù)狀態(tài)對象
state
,作為數(shù)據(jù)狀態(tài)的存儲,是一個匿名函數(shù)返回的對象,該對象中有一個curTheme
主題字符串和一個 curUser
用戶對象。
// state: 用戶相關(guān)狀態(tài) const state = () => ({ curTheme: 'light', curUser: { id: '123456', name: '張三' }, })
getters計(jì)算屬性對象
getters
,作為計(jì)算屬性,類似vue組件中的computed屬性。該對象中是一個個的方法函數(shù),該函數(shù)按照順序有(state, getters, rootState, rootGetters)
四個參數(shù)。前兩個參數(shù)state
和getters
是本模塊中的數(shù)據(jù)狀態(tài)對象和計(jì)算屬性對象,可在方法中按照如下格式進(jìn)行引用。
curUserId
中可以使用state.curUser
來訪問當(dāng)前模塊中數(shù)據(jù)狀態(tài)對象中的curUser對象。
使用方法:state.curUserId
isCurUserId
中返回的是一個函數(shù),該函數(shù)接收一個userId
參數(shù),通過getters.curUserId
可以訪問當(dāng)前計(jì)算屬性對象中的curUserId
屬性。
使用方法:state.isCurUserId(userId)
getCurThemeByUserId
中返回的是一個函數(shù),該函數(shù)接收一個userId
參數(shù),通過getters.isCurUserId(userId)
函數(shù)確認(rèn)是否是當(dāng)前用戶,并返回對應(yīng)主題。
使用方法:state.getCurThemeByUserId(userId)
const getters = { // 獲取當(dāng)前用戶ID curUserId: (state, getters, rootState, rootGetters) => { return state.curUser ? state.curUser.id : undefined }, // 比對userId是否是當(dāng)前用戶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 ''; } } }
后兩個參數(shù)rootState
和rootGetters
可以用來訪問根和其他模塊的數(shù)據(jù)狀態(tài)和計(jì)算屬性。比如下面是room
模塊的getters
屬性。
// room.js const getters = { // 測試 testRoom: (state, getters, rootState, rootGetters) => { // 獲取userid let curUserId = rootGetters['user/curUserId'] // 根據(jù)userId獲取當(dāng)前主題 let curTheme = rootGetters['user/getCurThemeByUserId'](curUserId) return 'test'; } }
actions異步請求對象
actions
,內(nèi)部是一個個函數(shù),所有異步操作需要放到這里,且如果需要更改數(shù)據(jù)狀態(tài),則必須通過commit
調(diào)用相應(yīng)的mutation
。且需要注意該函數(shù)有兩個參數(shù)(context, payload)
,其中context是一個對象,包括了state
,‘rootState’,‘commit’,‘dispatch’,‘getters’,'rootGetters’等參數(shù)。
需要注意的是,actions中的(context, payload)
參數(shù)中context是對象,所以里面的參數(shù)可以是無須的。但是getters中的(state, getters, rootState, rootGetters)
是四個參數(shù),并且是有序的,千萬注意順序!??!
// actions,異步操作,通過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 // } // 用戶登錄 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異步請求 dispatch('room/getRoomByUserId', null, { root: true }) // 調(diào)用rooom模塊的getRoomByUserId異步請求 // 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ù)同步對象
mutations
,數(shù)據(jù)同步對象,內(nèi)部是一個個同步函數(shù),該函數(shù)中主要是為了修改state屬性。注意千萬不要在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)
另外,對象和數(shù)組類型修改時不能使用state.curUser = curUser
這種方式。需要使用Vue.set()方法進(jìn)行修改,否則也不會觸發(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是存儲位置,可以是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不會隨著網(wǎng)頁刷新而丟失數(shù)據(jù),所以將Vuex數(shù)據(jù)狀態(tài)存儲在此解決刷新丟失數(shù)據(jù)的問題。如下圖,可以看到相應(yīng)的數(shù)據(jù)存儲。
另外,由于是明文存儲,可能存在安全問題,可以使用以下插件對數(shù)據(jù)進(jìn)行加密存儲。
var ls = new SecureLS({ encodingType: "aes", //加密類型 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), } }) ] })
加密之后,控制臺顯示如下,可以看到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", //加密類型 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: 用戶相關(guān)狀態(tài) const state = () => ({ curTheme: 'light', curUser: undefined, }) // getters: 用戶相關(guān)計(jì)算屬性,類似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 "無名氏" } } } // actions,異步操作,通過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 // } // 獲取用戶信息 async getCurUser ({ state, commit }) { // dispatch('room/getRoomByUserId', value, { root: true }) // 調(diào)用另外模塊 // 獲取登錄后存儲在localStorage中的token值 let token = localStorage.getItem("token"); // console.log("token=" + token) // 如果token為空則返回空 if(token == undefined || token == null) { commit('SET_CUR_USER', null); // Message.warning("用戶token失效,將移除本地token"); // 移除token localStorage.removeItem("token") // 關(guān)閉socket鏈接 websocket.close(); return; } try{ // 將token傳到后臺獲取對應(yīng)用戶信息 let res = await $api.getUserInfoByToken(); if(200 != res.code) throw new Error(res.message); localStorage.removeItem(SystemConfig.storageKey); // 登陸成功后清空會話緩存 // 獲取到用戶信息,設(shè)置到curUser狀態(tài)中 commit('SET_CUR_USER', res.data); // 設(shè)置websocket websocket.connect(state.curUser.id) } catch(err) { // 會話失效后應(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) } }, // 用戶登錄 async loginByUser({dispatch}, params) { try{ let res = await $api.login(params) // console.log("用戶登錄返回:", res) await dispatch('loginSuccess', res.data); }catch (err) { Message.error(err) return Promise.reject(err) } }, // 登錄之后的操作 async loginSuccess({state, dispatch}, token) { // 設(shè)置本地緩存 localStorage.token = token; // 獲取用戶信息 await dispatch('getCurUser'); // 如果獲取用戶信息成功,則打開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)目傳送門:https://github.com/louislee92/vue-module-persistedstate
到此這篇關(guān)于Vuex模塊化與持久化深入講解的文章就介紹到這了,更多相關(guān)Vuex模塊化與持久化內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS 實(shí)現(xiàn)獲取對象屬性個數(shù)的方法小結(jié)
這篇文章主要介紹了JS 實(shí)現(xiàn)獲取對象屬性個數(shù)的方法,結(jié)合實(shí)例形式總結(jié)分析了JS 獲取對象屬性個數(shù)的三種常用方法,需要的朋友可以參考下2023-05-05VUE 配置vue-devtools調(diào)試工具及安裝方法
vue-devtools是一款基于chrome瀏覽器的插件,用于vue應(yīng)用的調(diào)試,這款vue調(diào)試神器可以極大地提高我們的調(diào)試效率。幫助我們快速的調(diào)試開發(fā)vue應(yīng)用。這篇文章主要介紹了VUE 配置vue-devtools調(diào)試工具及安裝步驟 ,需要的朋友可以參考下2018-09-09Vue props 單向數(shù)據(jù)流的實(shí)現(xiàn)
這篇文章主要介紹了Vue props 單向數(shù)據(jù)流的實(shí)現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-11-11