通过Vuex实现全局状态管理

Vuex 是一个专为 Vue 应用程序开发的状态管理模式,通过集中式存储应用所有组件的状态,依照规则确保状态可以进行标准方式变化,比较适合应用在公共基础数据存放,如:用户信息

所属分类 WEB

相关标签 管理VueVuex

基础

# NPM
npm install vuex --save
# Yarn
yarn add vuex
# CDN
<script src="vuex.js"></script>

模块化打包系统中使用方式:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)

单一状态树,一个包含了全部应用层状态的数据源。

在一个应用中,state 只存在一个,这与多个模块并不冲突。

实际使用时,不同模块可以作为子树存放在 state 的不同属性中。

上文中提到 state 是一个单一状态树,如果业务中有多个模块涉及到全局状态,就可以通过 module 进行区分。

因为所有模块的全局状态都放在一个 state 内会使得代码变得臃肿,维护也会变得很困难。

访问/获取/计算全局状态属性的方法,它可以对原始全局状态属性进行计算后再返回。

与直接获取原始全局状态属性再计算不同之处在于,其计算结果会被缓存,只有原始全局状态属性变动后才会重新触发计算。

在 Vuex 想要变更全局状态属性,只可以通过提交 mutations 完成。

mutations 方法并不能直接调用,创建 mutations 更像是事件注册,实际调用使用 store.commit 或者注入到组件中去(推荐注入 Actions )。

actions 类似 mutations,但又有所不同。

actions 内最终提交 mutations 来完成对全局状态属性的修改,并且可以执行异步操作。

从使用角度来说,actions 内可以包含与服务端的交互,交互完成后提交 mutations 来完成最终的变更。

操作实例

介绍完基本概念后,我们通过一个实例来完成 Vuex 全局状态管理的演示。

一般在 main.js 中注册全局状态对象 store。

import Vue from 'vue'
// 建议为 store 单独建立一个目录
import store from './store'
// 路由
import router from './router'
// 注册使用
new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})

store 目录的 index.js 对全部模块进行统一管理。

import Vue from 'vue'
import Vuex from 'vuex'
// 引入统一 getters
import getters from './getters'
// 在 store/modules 目录下存放各个模块内容
import user from './modules/user'
import setting from './modules/setting'
// 统一管理
Vue.use(Vuex)
const store = new Vuex.Store({
  modules: { // 加入模块
    user,
    setting
  },
  getters // 加入 getters
})

export default store

getters 主要为了暴露 state 属性的值或者对内容进行计算,方便快速访问。

const getters = {
  // 直接返回
  name: state => state.user.name,
  token: state => state.user.token,
  // 包装计算返回
  settingHead:(state, getters) => { return state.setting.setArray.filter(item => item == 1) }
}
export default getters

一个全局状态属性通过 getters 包装后访问形式有所差异,比如上文的 settingHead。

// 没有包装计算,需要在组件中通过 computed 进行计算(每个组件都要写一遍)
computed: {
    settingHead() {
        return this.$store.state.setting.setArray.filter(item => item == 1)
    }
}
// getters 包装后,可以通过 computed 获取,也可以直接注入
computed: {
    settingHead() {
        return this.$store.state.getters.settingHead
    }
}
// 注入组件中,同时注入多个属性
import { mapGetters } from 'vuex'
/* ... */
computed: {
    ...mapGetters([ 'name', 'settingHead' ])
}

以下以 user 模块演示。

// 与服务端交互 API
import { login, getUserInfo } from '/src/api/user'
// 从浏览器 localStorage 存取 token
import { getToken, setToken, removeToken } from '/src/utils/auth'
// 模块的 state(名称、权限集、登录TOKEN)
const getDefaultState = () => {
  return { name: '', authKeys: [], token: getToken() }
}
const state = getDefaultState()
// mutations 操作
const mutations = {
  SET_USER: (state, user) => {
    state.name = user.name
    state.authKeys = user.authKeys
  },
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  RESET_USER: (state) => {
    Object.assign(state, getDefaultState())
  }
}
// actions 方法
const actions = {
  // 业务登录
  login({ commit }, loginInfo) {
    return new Promise((resolve, reject) => {
      // 调用API登录方法
      login(loginInfo).then(response => {
        const { data } = response
        if (!data) {
          reject('登录失败.')
        }
        // 登录成功,修改 state 的 TOKEN
        commit('SET_TOKEN', data)
        // 修改浏览器的 localStorage
        setToken(data)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },
  // 获取用户信息
  getInfo({ commit }) {
    return new Promise((resolve, reject) => {
      // TOKEN 存放在请求头中,无需提交
      getUserInfo().then(response => {
        const { data } = response
        if (!data) {
          reject('登录请求头不正确,请重新登录.')
        }
        const { authKeys, name } = data
        // 检查权限存在性
        if (!authKeys || authKeys.length <= 0) {
          reject('该账号没有任何访问权限.')
        }
        // 获取成功,写入 state
        commit('SET_USER', data)
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },
  // 登出
  logout({ commit, state }) {
    return new Promise((resolve, reject) => {
      // 前后端分离项目可以不调用 API
      // 移除 localStorage
      removeToken()
      // 重置 state
      commit('RESET_USER')
      resolve()
    })
  }
}

// 导出模块
export default {
  namespaced: true,
  state,
  mutations,
  actions
}

在具体业务组件代码中调用登录代码,实际调用的是 store->user->actions->login。

// 登录页面登录方法
methods: {
    login() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true
          // this.$store.dispatch('模块/actions方法')
          this.$store.dispatch('user/login', this.loginForm).then(() => {
            this.$router.push({ path: this.redirect || '/' })
            this.loading = false
          }).catch(() => {
            this.loading = false
            this.$message.error('账户密码错误或被锁定')
          })
        } else {
          this.$message.warning('请输入账号密码后提交')
          return false
        }
      })
    }
}

而例如获取用户信息的方法,在页面路由加载前就会触发。

import store from './store'
import router from './router'
router.beforeEach(async(to, from, next) => {
  // 当前是登录页无需继续执行后续操作
  if(store.getters.token) {
     // 是否有权限
     if(store.getters.authKeys && store.getters.authKeys.length > 0) {
        next()
      } else {
        try {
          // 获取用户信息(store.dispatch('user/getInfo'))
          const { authKeys } = await store.dispatch('user/getInfo')
          // 根据权限集更新动态路由
          const accessRoutes = await store.dispatch('router/accessRoutes', authKeys)
          router.addRoutes(accessRoutes)
          next({ ...to, replace: true })
        } catch (error) {
          // 获取异常,触发登出重置数据
          await store.dispatch('user/logout')
          Message.error(error || '产生异常')
          next({ path: '/login' })
          NProgress.done()
        }
      }
    }
  } else {
     // 位于登录页
     if(to.path.indexOf('login') !== -1) {
        next()
     } else {
        // 未登录
         next({ path: '/login' })
     }
  } 
})

米虫

做一个有理想的米虫,伪全栈程序猿,乐观主义者,坚信一切都是最好的安排!

本站由个人原创、收集或整理,如涉及侵权请联系删除

本站内容支持转发,希望贵方携带转载信息和原文链接

本站具有时效性,不提供有效、可用和准确等相关保证

本站不提供免费技术支持,暂不推荐您使用案例商业化

发表观点

提示

昵称

邮箱

QQ

网址

当前还没有观点发布,欢迎您留下足迹!

同类其他

WEB

nvm管理node.js和npm多版本切换

在业务中我们会出现不同的项目依赖与不同版本的 node.js,总不能每次跑项目的时候都去重新安装对应版本的 node.js 和依赖,使用 nvm 可以让多个版本的 node.js 共存,并提供管理和切换

Jquery中bind()、live()、delegate()和on()的区别

自Jquery1.7起,on()方法是 bind()、live()、delegate() 方法的新的替代品,我们推荐使用on()来处理业务中的事件绑定,通过理解这些方法的差异能够更加清晰明白使用on()方法的优势所在

Jquery.bind()实现前端字段公共校验器

严谨的页面开发需要着重关注前台校验相关的内容,确保请求参数的合法以保证服务器安全,界面参数众多需要建立一个公共方法,公共校验器的核心方法是Jquery.bind()

Jquery+CSS轻松实现导航动态显示隐藏

绝大多数网站都会有个顶部导航,对于手机端而言为了便于访问导航常常会做固顶操作,通过CSS样式配合Jquery的scroll()方法或原生JS监听滑动事件方法,可以轻松实现下滑隐藏,上滑显示的效果

前端JS对字段编码预防XSS攻击

跨站脚本攻击是比较严重的一种攻击行为,恶意脚本注入到相关页面字段中轻易获取敏感信息和向后端发起请求,前端应用应当对请求数据进行编码

SVGInject插件动态加载SVG并自定义样式

HTML 中加载 SVG 有很多种方式,但如果需要在 HTML 中通过 CSS 样式自由控制 SVG 样式就必须将 svg 标签插入网页找那个成为 DOM 的一部分,本文借助 SVGInject 插件可以快速完成这一操作

选择个人头像

昵称

邮箱

QQ

网址

评论提示

  • 头像:系统为您提供了12个头像自由选择,初次打开随机为你选择一个
  • 邮箱:可选提交邮箱,该信息不会外泄,或将上线管理员回复邮件通知
  • 网址:可选提交网址,评论区该地址将以外链的形式展示在您的昵称上
  • 记忆:浏览器将记忆您已选择或填写过得信息,下次评论无需重复输入
  • 审核:提供一个和谐友善的评论环境,本站所有评论需要经过人工审核