我们在使用Vue开发项目时,如果项目比较复杂的话,使用Vuex进行状态管理会大大简化开发难度,提高开发效率。
但是当使用Vuex的一些复杂功能(如:模块、命名空间)时,Vuex会变的稍微有些复杂。再加上Vuex的灵活性,同样的功能可以有很多种实现方式。
因为太灵活、实现方式太多,导致经常会记混掉
。
所以就整理了一下Vuex的几种常用的使用方式以实例作个对比
。
一、基础概念
1.1、Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。
1.2、vuex中的模块(module)
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
1.3、模块的命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
二、vuex的常规用法(不使用模块(module)的store)
2.1、创建一个简单的store
store/index.js的内容
import Vue from 'vue'
import Vuex from 'vuex'
import api from '@/api/index'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
loading : false, // 是否加载中(接口请求时的全屏loading效果)
userInfo: {} // 用户信息
},
getters: {
loading: state => state.loading,
userInfo: state => state.userInfo,
userName: state => state.userInfo.userName || ''
},
mutations: {
setLoading(state, payload) {
state.loading = payload
},
setUserInfo(state, payload) {
state.userInfo = payload
}
},
actions: {
// 获取用户信息
queryUserInfoAction({ commit }) {
api.queryUserInfo().then((res) => {
let result = res && res.result || {}
commit('setUserInfo', result)
})
}
}
})
export default store
2.2、在组件中使用
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
// 【传统方式】获取store中的数据
/*
loading() {
return this.$store.getters.loading
},
userName() {
return this.$store.getters.userName
},
*/
// 【辅助函数方式】获取store中的数据(代码更简洁)
...mapGetters(['loading', 'userName']),
},
created() {
// 如果没有用户名,则查询用户信息,已有则不需查询(减少不必要的http请求)
// 【传统方式】请求异步数据
!this.userName && this.$store.dispatch('queryUserInfoAction')
// 【辅助函数方式】请求异步数据
!this.userName && this.queryUserInfoAction()
},
methods: {
...mapActions(['queryUserInfoAction']), // 查询用户信息的aciton
// 打开loading效果
openLoading() {
this.$store.commit('setLoading', true)
},
// 关闭loading效果
closeLoading() {
this.$store.commit('setLoading', false)
}
}
}
三、vuex中模块的基础使用(不开启命名空间时)
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的
这样使得多个模块能够对同一 mutation 或 action 作出响应。
对于action、mutation 和 getter,是在模块中还是在全局中,它们的使用方式是相同的,只是state会有所不同,模块中的state会多一层模块名。格式变成store.state.模块名.状态名(根state中的格式为store.state.状态名)。
3.1、目录结构
├── api # api请求目录
│ ├── index.js # 所有api请求
├── store # store目录
│ ├── modules # store中的所有模块
│ │ ├── theme.js # 主题模块
│ │ ├── product.js # 产品模块
│ ├── index.js # store主文件
3.2、创建一个带模块的store
store主文件(store/index.js)内容
import Vue from 'vue'
import Vuex from 'vuex'
import api from '@/api/index'
import themeModule from './modules/theme.js' // 主题模块
import productModule from './modules/product.js' // 产品模块
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
loading : false, // 是否加载中(接口请求时的全屏loading效果)
userInfo: {} // 用户信息
},
getters: {
loading: state => state.loading,
userInfo: state => state.userInfo,
userName: state => state.userInfo.userName || ''
},
mutations: {
setLoading(state, payload) {
state.loading = payload
},
setUserInfo(state, payload) {
state.userInfo = payload
}
},
actions: {
// 获取用户信息
queryUserInfoAction({ commit }) {
api.queryUserInfo().then((res) => {
let result = res && res.result || {}
commit('setUserInfo', result)
})
}
},
modules: {
themeModule, // 主题模块
productModule, // 产品数据
}
})
export default store
主题模块(store/modules/theme.js)内容
import api from '@/api/index'
const themeModule = {
state: {
themeObj: {} // 主题数据
},
getters: {
themeData: state => {
return {
primaryColor: '#fe3132', // 主颜色(如:按钮背景颜色)
subBgColor: '#fff6f6', // 次要颜色(如:浅色背景色)
...state.themeObj // 没有返回数据使用以上默认值,有则覆盖以上数据
}
}
},
mutations: {
setTheme(state, payload) {
state.themeObj = payload
}
},
actions: {
queryThemeAction({ commit }) {
return api.queryTheme().then(res => {
let data = (res && res.result) || {}
commit('setTheme', data)
})
}
}
}
export default themeModule
产品模块(store/modules/product.js)内容
import api from '@/api/index'
const productModule = {
state: {
proData: {}, // 产品数据
indexDataRes: {} // 产品首页数据
},
getters: {
proName: state => state.proData.proName || '', // 产品名称
proDesc: state => state.proData.proDesc || '' // 产品描述
indexData: state => state.indexDataRes // 产品首页数据
},
mutations: {
// 设置产品数据
setProData(state, payload) {
state.proData = payload
},
// 设置产品首页数据
setIndexData(state, payload) {
state.indexDataRes = payload
},
},
actions: {
// 获取产品数据
queryProDataAction(context) {
return api.queryProData().then(res => {
// 页面数据
let data = (res && res.result) || {}
context.commit('setProData', data)
})
},
// 获取首页数据
queryIndexDataAction(context) {
return api.queryIndexData().then(res => {
let data = (res && res.result) || {}
context.commit('setIndexData', data)
})
}
}
}
export default productModule
3.3、在组件中使用
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
// 【传统方式】获取store中的数据
// 注意state和getter数据结构的区别(state需要带上模块名,而getter不需要模块名)
/*
proData() {
return this.$store.state.productModule.proData
},
themeData() {
return this.$store.getters['themeData']
},
proName() {
return this.$store.getters['proName']
},
proDesc() {
return this.$store.getters['proDesc']
},
indexData() {
return this.$store.getters['indexData']
},
*/
// 【辅助函数方式】获取store中的数据(代码更简洁)
...mapState({ proData: state => state.productModule.proData }),
...mapGetters(['themeData']),
...mapGetters(['proName', 'proDesc', 'indexData'])
},
created() {
// 【传统方式】获取异步数据
// this.$store.dispatch('queryThemeAction') // 获取主题数据
// this.$store.dispatch('queryProDataAction') // 获取产品数据
// this.$store.dispatch('queryIndexDataAction') // 获取首页数据
// 【辅助函数方式】获取异步数据(需要先在methods中使用mapActions定义方法)
this.queryThemeAction() // 获取主题数据
this.queryProDataAction() // 获取产品数据
this.queryIndexDataAction() // 获取首页数据
},
methods: {
...mapActions(['queryThemeAction']),
...mapActions(['queryProDataAction', 'queryIndexDataAction'])
}
}
四、vuex中【开启命名空间】的模块
如果你想模块之间相互独立、互不影响。可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 和 mutation 都会自动根据模块注册的路径调整命名。所以开启命名空间的模块中的getter、action 和 mutation的使用方式都会改变。
但是开启命名空间和不开启命名空间的模块中的state的使用方式不会改变。格式依然是store.state.模块名.状态名。
4.1、开启模块的命名空间
const moduleName = {
state: {
// 此处代码省略...
},
getters: {
// 此处代码省略...
},
mutations: {
// 此处代码省略...
},
actions: {
// 此处代码省略...
},
namespaced: true // 开启命名空间
}
export default moduleName
4.2、在组件中使用
import { mapState, mapGetters, mapActions } from 'vuex'
export default {
computed: {
// 【传统方式】获取store中的数据
/*
proData() {
return this.$store.state.productModule.proData
},
themeData() {
return this.$store.getters['themeModule/themeData']
},
proName() {
return this.$store.getters['productModule/proName']
},
proDesc() {
return this.$store.getters['productModule/proDesc']
},
indexData() {
return this.$store.getters['productModule/indexData']
},
*/
// 【辅助函数方式一】获取store中的数据(代码更简洁)
/*
//需要重命名
...mapState({ proData: state => state.productModule.proData }),
//不需要重命名
...mapState(['productModule/proData']),
...mapGetters(['themeModule/themeData']),
...mapGetters(['themeModule/proName', 'themeModule/proDesc', 'themeModule/indexData'])
*/
// 【辅助函数方式二】获取store中的数据(代码最简洁)
// 将模块的空间名称字符串作为第一个参数传递给辅助函数,这样所有绑定都会自动将该模块作为上下文。
//需要重命名
...mapState('productModule', { proData: state => state.proData }),
//不需要重命名
...mapState('productModule',['proData']),
...mapGetters('themeModule', ['themeData']),
...mapGetters('productModule', ['proName', 'proDesc', 'indexData'])
},
created() {
// 【传统方式】获取异步数据
// this.$store.dispatch('themeModule/queryThemeAction') // 获取主题数据
// this.$store.dispatch('productModule/queryProDataAction') // 获取产品数据
// this.$store.dispatch('productModule/queryIndexDataAction') // 获取首页数据
// 【辅助函数方式】获取异步数据(需要先在methods中使用mapActions定义方法)
this.queryThemeAction() // 获取主题数据
this.queryProDataAction() // 获取产品数据
this.queryIndexDataAction() // 获取首页数据
},
methods: {
...mapActions('themeModule', ['queryThemeAction']),
...mapActions('productModule', ['queryProDataAction', queryIndexDataAction])
}
}
五、扩展
模块:
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能会变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
命名空间:
默认情况下,模块内部的 action、mutation、getter是注册在全局命名空间的 — 这样使得多个模块能够对同一 mutation 或 action 做出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced:true 的方式使其成为带命名的模块。当模块被注册后,他所有 getter、action、及 mutation 都会自动根据模块注册的路径调整命名。