Vuex模块化与持久化深入讲解
路易斯·李 人气:0概述
Vuex作为VUE状态管理组件,能够将项目公共数据进行统一管理。而且可以按照不同的业务功能将数据状态分模块管理。另外,对于网页刷新导致Vuex状态丢失的问题可以使用vuex-persistedstate
插件配置将数据保存在localStorage
或者sessionStorage
中。
本文测试环境如下:
“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参数,用于实例化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状态实例,使用new Vuex.Store()
进行状态实例化,并将根状态的state
,getters
,mutations
,actions
添加到参数,以及各个模块添加到modules
对象参数中。
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 = {} // 实例化状态对象 export default new Vuex.Store({ state, getters, mutations, actions, modules: { // 将各个模块放入modules属性中 user, room, chat } })
以上./store/index.js
文件中,有user
,room
,chat
三个模块,模块之间大同小异,下面仅以user
模块进行讲解。其中
state数据状态对象
state
,作为数据状态的存储,是一个匿名函数返回的对象,该对象中有一个curTheme
主题字符串和一个 curUser
用户对象。
// state: 用户相关状态 const state = () => ({ curTheme: 'light', curUser: { id: '123456', name: '张三' }, })
getters计算属性对象
getters
,作为计算属性,类似vue组件中的computed属性。该对象中是一个个的方法函数,该函数按照顺序有(state, getters, rootState, rootGetters)
四个参数。前两个参数state
和getters
是本模块中的数据状态对象和计算属性对象,可在方法中按照如下格式进行引用。
curUserId
中可以使用state.curUser
来访问当前模块中数据状态对象中的curUser对象。
使用方法:state.curUserId
isCurUserId
中返回的是一个函数,该函数接收一个userId
参数,通过getters.curUserId
可以访问当前计算属性对象中的curUserId
属性。
使用方法:state.isCurUserId(userId)
getCurThemeByUserId
中返回的是一个函数,该函数接收一个userId
参数,通过getters.isCurUserId(userId)
函数确认是否是当前用户,并返回对应主题。
使用方法:state.getCurThemeByUserId(userId)
const getters = { // 获取当前用户ID curUserId: (state, getters, rootState, rootGetters) => { return state.curUser ? state.curUser.id : undefined }, // 比对userId是否是当前用户id isCurUserId: (state, getters, rootState, rootGetters) => { return (userId) => { return userId == getters.curUserId; } }, // 根据userId获取当前主题 getCurThemeByUserId: (state, getters, rootState, rootGetters) => { return (userId) => { if(getters.isCurUserId(userId)) return state.curTheme; else return ''; } } }
后两个参数rootState
和rootGetters
可以用来访问根和其他模块的数据状态和计算属性。比如下面是room
模块的getters
属性。
// room.js const getters = { // 测试 testRoom: (state, getters, rootState, rootGetters) => { // 获取userid let curUserId = rootGetters['user/curUserId'] // 根据userId获取当前主题 let curTheme = rootGetters['user/getCurThemeByUserId'](curUserId) return 'test'; } }
actions异步请求对象
actions
,内部是一个个函数,所有异步操作需要放到这里,且如果需要更改数据状态,则必须通过commit
调用相应的mutation
。且需要注意该函数有两个参数(context, payload)
,其中context是一个对象,包括了state
,‘rootState’,‘commit’,‘dispatch’,‘getters’,'rootGetters’等参数。
需要注意的是,actions中的(context, payload)
参数中context是对象,所以里面的参数可以是无须的。但是getters中的(state, getters, rootState, rootGetters)
是四个参数,并且是有序的,千万注意顺序!!!
// actions,异步操作,通过mutation进行更新数据 const actions = { //context:{ // state, 等同于store.$state,若在模块中则为局部状态 // rootState, 等同于store.$state,只存在模块中 // commit, 等同于store.$commit // dispatch, 等同于store.$dispatch // getters 等同于store.$getters,若在模块中为局部状态 // rootGetters 等同于store.$getters // } // 用户登录 async login ({state, rootState, commit, dispatch, getters, rootGetters}, {name, passwd}) { // getters使用 getters.curUserId // 获取当前模块中的curUserId计算属性 rootGetters['user/curUserId'] // 获取user模块中的curUserId计算属性 // dispatch使用 dispatch('logout') // 调用本模块中的logout异步请求 dispatch('room/getRoomByUserId', null, { root: true }) // 调用rooom模块的getRoomByUserId异步请求 // commit使用 commit('SET_CUR_USER', null) // 调用本模块mutation方法 commit('room/SET_ROOM_LIST', null, { root: true }) // 调用room模块mutation方法 }, // 登出 async logout({commit}) { let res = await $api.logout() localStorage.removeItem("token"); commit("SET_CUR_USER", null); // 关闭socket链接 websocket.close(); await $router.push("/") },
mutations数据同步对象
mutations
,数据同步对象,内部是一个个同步函数,该函数中主要是为了修改state属性。注意千万不要在actions
或者其他地方直接设置state
数据状态,若要修改state
状态,必须使用commit
。因为只有在mutations
方法中修改才能触发Vuex数据和视图同步更新。
其他地方更新数据,需要使用commit方法
commit('room/SET_ROOM_LIST', null)
另外,对象和数组类型修改时不能使用state.curUser = curUser
这种方式。需要使用Vue.set()方法进行修改,否则也不会触发数据视图的更新。
Vue.set(state, 'curUser', curUser) Vue.set(state.curUser, 'name', '张三') Vue.set(state.list, 0, "2");
// mutations,定义更新数据方法,同步操作 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属性,并设置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, }) ] })
因为localStorage不会随着网页刷新而丢失数据,所以将Vuex数据状态存储在此解决刷新丢失数据的问题。如下图,可以看到相应的数据存储。
另外,由于是明文存储,可能存在安全问题,可以使用以下插件对数据进行加密存储。
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中内容已加密。
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: 用户相关状态 const state = () => ({ curTheme: 'light', curUser: undefined, }) // getters: 用户相关计算属性,类似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进行更新数据 const actions = { //context:{ // state, 等同于store.$state,若在模块中则为局部状态 // rootState, 等同于store.$state,只存在模块中 // commit, 等同于store.$commit // dispatch, 等同于store.$dispatch // getters 等同于store.$getters // } // 获取用户信息 async getCurUser ({ state, commit }) { // dispatch('room/getRoomByUserId', value, { root: true }) // 调用另外模块 // 获取登录后存储在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") // 关闭socket链接 websocket.close(); return; } try{ // 将token传到后台获取对应用户信息 let res = await $api.getUserInfoByToken(); if(200 != res.code) throw new Error(res.message); localStorage.removeItem(SystemConfig.storageKey); // 登陆成功后清空会话缓存 // 获取到用户信息,设置到curUser状态中 commit('SET_CUR_USER', res.data); // 设置websocket websocket.connect(state.curUser.id) } catch(err) { // 会话失效后应该清理本地缓存 localStorage.removeItem("token"); // 关闭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) { // 设置本地缓存 localStorage.token = token; // 获取用户信息 await dispatch('getCurUser'); // 如果获取用户信息成功,则打开websocket并进入大厅 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); // 关闭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,定义更新数据方法,同步操作 // const mutations = { SET_CUR_USER (state, curUser) { Vue.set(state, 'curUser', curUser) }, } export default { namespaced: true, state, getters, mutations, actions }
项目传送门:https://github.com/louislee92/vue-module-persistedstate
加载全部内容