通过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

CSS文字超出隐藏添加省略号及失效处理

在前端页面中文字超出隐藏并添加省略号是比较常见的样式处理,实际CSS样式中单行省略和多行省略的样式有所差异,在某些特殊场景下多行超出的样式会不生效,本文给出相关解决方案

CSS3通过Gradients实现渐变背景色

使用背景色渐变可以使得页面质感得到大幅度提升,CSS样式中通过gradients属性指定两个或多个颜色之间平滑过渡,由于颜色的渐变是由浏览器生成,因此不会出现不同分辨率下失真的情况。

侧边栏下滑即将越过时动态固顶与上滑恢复

对于很多二栏布局的网站而言,正文部分过长时,侧边栏区域下拉过程中出现大片空白,因此我们可以实现当屏幕下滑达到指定元素位置(如即将越过)后对元素固顶来解决侧边栏区域的空旷感

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

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

Vue3中无需引入Vuex的替代方案

Vue3 中所提供组合 API、ref、reactive、provide、inject几大特性,可以支持完成全局状态管理能力,Vuex 是为 Vue 单独提供的状态管理插件,相对比较笨重,如果项目简单可以尝试替代

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

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

选择个人头像

昵称

邮箱

QQ

网址

评论提示

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