亲宝软件园·资讯

展开

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)四个参数。前两个参数stategetters是本模块中的数据状态对象和计算属性对象,可在方法中按照如下格式进行引用。

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 '';
	}
  }
}

后两个参数rootStaterootGetters可以用来访问根和其他模块的数据状态和计算属性。比如下面是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属性,并设置keystorage属性,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

加载全部内容

相关教程
猜你喜欢
用户评论