vue.js

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript类库 > vue.js > Vuex模块化与持久化

Vuex模块化与持久化深入讲解

作者:路易斯·李

在实际项目开发过程中,如果公共数据比较多我们会使用vuex做公共状态管理,但是在对浏览器进行刷新操作的时候,会导致vuex内的数据丢失,这种情况有些时候是没问题的,但是有的时候我们需要某些数据可以持久化的保存,这样就需要做对应的处理

概述

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

到此这篇关于Vuex模块化与持久化深入讲解的文章就介绍到这了,更多相关Vuex模块化与持久化内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文