Vuex 是一个专为 Vue 应用程序开发的状态管理模式,通过集中式存储应用所有组件的状态,依照规则确保状态可以进行标准方式变化,比较适合应用在公共基础数据存放,如:用户信息
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
# 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' })
}
}
})
温馨提示:系统将通过浏览器临时记忆您曾经填写的个人信息且支持修改,评论提交后仅自己可见,内容需要经过审核后方可全面展示。