Java技术大杂烩
,欢迎关注查看更多原创技术文章在上篇文章
从零搭建 Vue 开发环境
中,学习了 Vue 的语法,如何使用 Vue 进行开发,学会了如何搭建开发环境,打包部署等;文章中也介绍了兄弟组件之间传值是通过 Vuex 来实现的,只不过是进行了简单的应用,今天就来详细的深入学习如何使用 Vuex 来进行状态管理。
Vuex 是专门为 Vue.js 设计的状态管理库,它集中存储,管理所有组件的状态;通过上篇文章的学习,我们知道父组件要把值传递给子组件的时候,可以通过 props 来传递,子组件要把值传递给父组件的时候,可以通过事件的形式来实现,而对于兄弟组件来说,就需要用到 Vuex 来实现了。也就是一个组件把值放入到 Vuex 中,另一个组件从中取值从而实现参数传递的效果。
一个应用中,Vuex 用一个对象就包含了全部应用层级的状态,它作为一个“唯一数据源 (SSOT)”而存在。也就是说,每个应用将仅仅包含一个 store 实例。所以它应该是一个全局单例模式。
首先要执行 npm install vuex --save 命令安装 Vuex
然后在 src
下创建 store
文件夹,在 store
文件夹内创建 index.js
文件,就在 index.js 里写 Vuex 。
最后在 main.js
中进行注册即可。
import Vue from 'vue'
import App from './App.vue'
import store from './store'
new Vue({
store,
render: h => h(App)
}).$mount('#app')
一个完整的 store 的 index.js 文件如下:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
// 所有的状态
state: {
userName: '张三',
userAge: 2
},
// 改变状态的操作
mutations: {
setUserName(state, aUserName) {
state.userName = aUserName;
},
setUserAge(state, aUserAge) {
state.userAge = aUserAge;
},
},
// 相等于 vue 的计算属性
getters: {
getAgeOfNextYear(state) {
return state.userAge += 1;
}
},
// 和 mutations类似, 支持异步操作
actions: {
getAgeOfNextYear(context) {
context.commit('getAgeOfNextYear');
}
}
})
export default store // 需要在 main.js 中注册
一个完整的 store 的 index.js 文件主要有 state, mutations, getters 和 actions 4项内容,下面依次来说说每个项的作用和用法。
state 用来存放所有组件的状态,每个组件都可以读取和修改状态;但是,并不是每个状态都适合放在 state 里面,如果是单个组件私有的状态,最好还是作为组件的局部状态。放在 state 里面的状态一般是多个组件共享的。
const store = new Vuex.Store({
// 所有的状态
state: {
userName: '张三',
userAge: 2,
address: '四川省成都市',
job: 'Java'
},
放在state里面的状态,在组件中,怎么获取值呢,是通过 this.$store.state.xxx
来获取的,如下所示,获取这几个 state 的值,并输出到控制台上:
export default {
data() {
return {
userName: this.$store.state.userName,
userAge: this.$store.state.userAge,
address: this.$store.state.address,
job: this.$store.state.job,
}
},
created() {
console.info(this.userName);
console.info(this.userAge);
console.info(this.address);
console.info(this.job);
}
}
控制台输出如下:
如果安装了 Vue 的 chrome 开发插件 Vue Devtools ,也可以看到 state 里面的状态值:
在 Vuex 中,Getter 的作用类似于 Vue 的计算属性的概念,可以对 state 里面的值进行计算,从而在组件调用的时候,不用每个组件都要重新计算,有点像 Java 里面的公共方法一样。
Getter里面的方法的第一个参数必须为 state
比如,我们要计算 state 里面的 userAge
这个状态值,让它返回明年的年龄:
const store = new Vuex.Store({
// 所有的状态
state: {
userName: '张三',
userAge: 2,
address: '四川省成都市',
job: 'Java'
},
getters: {
getAgeOfNextYear(state) {
return state.userAge += 1;
}
},
在组件中通过 this.$store.getters.xxx
来调用,如下所示 :
export default {
data() {
return {
userAge: this.$store.state.userAge,
ageOfNextYear: this.$store.getters.getAgeOfNextYear,
}
},
created() {
console.info(this.userAge);
console.info(this.ageOfNextYear);
}
}
控制台和 Devtools 输出如下:
可以看到,执行了 Getter 里面的方法之后,state 里面的值也改变了。
在组件中通过 this.$store.state.xxx
来获取状态的值,但是怎么改变它的值呢?是不能直接通过 this.$store.state.xxx = XXX
来改变状态值的,而是需要通过 Mutation 来改变的。
const store = new Vuex.Store({
state: {
userName: '张三',
userAge: 2,
address: '四川省成都市',
job: 'Java'
},
mutations: {
setUserName(state, aUserName) {
state.userName = aUserName;
},
setUserAge(state, aUserAge) {
state.userAge = aUserAge;
},
},
在组件中通过 this.$store.commit('xxx', 'param') 来调用:
export default {
data() {
return {
userName: this.$store.state.userName,
userAge: this.$store.state.userAge,
}
},
created() {
console.info(this.userName);
console.info(this.userAge);
// 改变名字和年龄
this.$store.commit('setUserName', '李四')
this.$store.commit('setUserAge', 5)
console.info(this.$store.state.userName);
console.info(this.$store.state.userAge);
}
}
控制台输出如下,可以看到改变已经生效了:
Vuex 的 store 中的状态是响应式的,也就是说当我们变更状态时,监视状态的 Vue 组件也会自动更新。
还有一点需要注意的是 Mutation 中的操作是同步的。
Action 类似于 mutation,也是用来改变 state 中的状态值,不同的地方在于:
mutations: {
setUserName(state, aUserName) {
state.userName = aUserName;
},
setUserAge(state, aUserAge) {
state.userAge = aUserAge;
},
},
actions: {
setUserName(context) {
context.commit('setUserName');
},
setUserAge(context) {
context.commit('setUserAge');
}
}
调用:this.$store.dispatch('xxx')
上面说了所有组件的状态都需要放在 state 中,试想一下,如果有很多状态需要维护,把所有的状态都放在 state 中,是不是不好维护?就像 Java 中也不会把所有的类写在一个包下,也是分包存放的,这样有利于阅读和维护。
Vuex 提供了 Module的概念,我们可以把 store 分割成多个模块(Module),每个 Module 都有自己的 state, mutations, getters 和 actions;这个 Module 可以根据功能模块来划分,也可以根据业务需求来划分。
const userManagerModule = {
state: {
userId: '11111',
userName: '张三',
userAge: 2,
},
mutations: {
setUserName(state, aUserName) {
state.userName = aUserName;
},
setUserAge(state, aUserAge) {
state.userAge = aUserAge;
},
},
getters: {},
actions: {}
}
const goodsManagerModule = {
state: {
goodsName: '衣服',
goodsPrice: 3,
},
mutations: {
setUserName(state, aUserName) {
state.userName = aUserName;
},
setUserAge(state, aUserAge) {
state.userAge = aUserAge;
},
},
getters: {},
actions: {}
}
const store = new Vuex.Store({
modules: {
userManager: userManagerModule,
goodsManager: goodsManagerModule
}
})
export default store // 需要在 main.js 中注册
然后通过 this.$store.state.moduleName.xxx
来获取对应 Module 的状态。
export default {
data() {
return {
userName: this.$store.state.userManager.userName,
userAge: this.$store.state.userManager.userAge,
goodsName: this.$store.state.goodsManager.goodsName,
goodsPrice: this.$store.state.goodsManager.goodsPrice,
}
},
created() {
console.info(this.userName);
console.info(this.userAge);
console.info(this.goodsName);
console.info(this.goodsPrice);
}
}
改变对应 Module 的状态值还是通过 this.$store.commit(‘xxx’, ‘param’)
来实现:
export default {
data() {
return {
userName: this.$store.state.userManager.userName,
userAge: this.$store.state.userManager.userAge,
goodsName: this.$store.state.goodsManager.goodsName,
goodsPrice: this.$store.state.goodsManager.goodsPrice,
}
},
created() {
console.info(this.userName);
console.info(this.userAge);
this.$store.commit('setUserName', '李四');
this.$store.commit('setUserAge', 5);
console.info(this.$store.state.userManager.userName);
console.info(this.$store.state.userManager.userAge);
console.info(this.goodsName);
console.info(this.goodsPrice);
this.$store.commit('setGoodsName', '裤子');
this.$store.commit('setGoodsPrice', 6);
console.info(this.$store.state.userManager.userAge);
console.info(this.$store.state.goodsManager.goodsName);
console.info(this.$store.state.goodsManager.goodsPrice);
}
}
但是如果多个 Module 之间的 mutations 中有同名的方法,执行 this.$store.commit('xxx', 'param')
后,哪个 Module 会生效呢?接下来看个例子:
const userManagerModule = {
state: {
userId: '11111',
userName: '张三',
userAge: 2,
},
mutations: {
setUserId(state, userId) {
state.userId = userId;
},
},
}
const goodsManagerModule = {
state: {
userId: '22222',
goodsName: '衣服',
goodsPrice: 3,
},
mutations: {
setUserId(state, userId) {
state.userId = userId;
},
},
}
现在两个 Module 都有 userId
属性,且分别为 11111
和 22222
,此外,都有改变 userId
的方法 setUserId,控台台输出如下所示:
export default {
data() {
return {
userManagerModuleUserId: this.$store.state.userManager.userId,
goodsManagerModuleUserId: this.$store.state.goodsManager.userId,
}
},
created() {
console.info(this.userManagerModuleUserId);
console.info(this.goodsManagerModuleUserId);
}
}
现在来改变 userId 的值为 88888,之后再次输出:
export default {
data() {
return {
userManagerModuleUserId: this.$store.state.userManager.userId,
goodsManagerModuleUserId: this.$store.state.goodsManager.userId,
}
},
created() {
console.info(this.userManagerModuleUserId);
console.info(this.goodsManagerModuleUserId);
this.$store.commit('setUserId', '88888');
console.info(this.$store.state.userManager.userId);
console.info(this.$store.state.goodsManager.userId);
}
}
所以,分了 Module 之后,还需要注意这点,不然一个 Module 不小心修改了之后,出现问题了都不好排查。
这是因为默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
要解决这个问题,就需要使用命名空间 namespaced 来实现,在创建 Module 的时候,把 namespaced属性设置为 true
,这样当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名
如下所示:
const userManagerModule = {
namespaced:true,
state: {
userId: '11111',
userName: '张三',
userAge: 2,
},
mutations: {
setUserId(state, userId) {
state.userId = userId;
},
},
}
const goodsManagerModule = {
namespaced:true,
state: {
userId: '22222',
goodsName: '衣服',
goodsPrice: 3,
},
mutations: {
setUserId(state, userId) {
state.userId = userId;
},
},
}
此时 state 的值为:
现在来改变每个 Module 的 userId 的值:
export default {
data() {
return {
userManagerModuleUserId: this.$store.state.userManager.userId,
goodsManagerModuleUserId: this.$store.state.goodsManager.userId,
}
},
created() {
console.info('修改之前 userManager模块的 userId = ' + this.userManagerModuleUserId);
console.info('修改之前 goodsManager模块的 userId = ' + this.goodsManagerModuleUserId);
// 改变 userManager 模块下的 userId 的值
this.$store.commit('userManager/setUserId', '44444');
// 改变 goodsManager 模块下的 userId 的值
this.$store.commit('goodsManager/setUserId', '88888');
console.info('修改之后 userManager模块的 userId = ' + this.$store.state.userManager.userId);
console.info('修改之后 goodsManager模块的 userId = ' + this.$store.state.goodsManager.userId);
}
}
控制台输出如下:
通过使用命名空间 namespaced 就可以解决多个 Module 下 mutations 同名的问题啦。
同样,多个 Module 下的 Getter, Action 下的同名方法也是如此
Action:
dispatch('moduleName/functionName')
Getter:
getters['moduleName/functionName']
到这里 Vuex 的学习就结束啦,由于之前项目时间紧,大概学会了如何用 Vuex 就直接上手写代码了,没有深入了解,只是简单的 state 和 mutations,也没有分 Module,后面的业务中由于需要加更多的渠道进来,那后面就需要根据渠道来分 Module 了,每个渠道管理自己的状态。
此外,还了解了多个 Module 下同名的 mutations, Getter, Action 的处理方式,也算是避免了一个坑。
看来学习新知识还是要花时间去深入学习,才能写出易于维护,高质量的代码呀。