Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
1. Vuex介绍
Vuex是官方的状态管理工具,主要概念有State、Getter、Mutation、Action、Module。
文章的开头说过Vue的组件都是一个个Vue实例,Vuex也可以这样来看,具体对比关系如下:
Vuex | 主要作用 | Vue组件 |
---|---|---|
State | 用来保存状态 | 相当于data属性 |
Getter | 用来对属性进行组合修改 | 相当于计算属性 |
Mutation | 常用来直接改变State的值 | 相当于methods |
Action | 主要用于提交Mutations,并且可以处理异步 | - |
Module | 用于模块化状态管理 | 模块化Vue组件 |
使用Vuex,我们用State保存状态,使用Getter来对状态数据进行处理,使用Mutation来直接改变State的值,用Action来提交Mutation,Action不是另类的Mutation,他操作的是Mutation中的函数,按照规范,Action是不能直接修改State的,虽然它可以修改State,如果需要返回一个状态或者一个异步的回调,比如在Actions里面进行了http请求,可以直接返回一个Promise,使用Module,来对庞大的项目进行分组。
2. 对Vuex中State的获取
一般情况下都会把Vuex全局注入到Vue中,这时每一个Vue实例都能访问到,一般都会使用计算属性来接受Vuex的值,代码如下:
//定义一个简单的Vuex
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {
Title: "123",
},
mutations: {
changeTitle(state, newTitle) {
state.Title= newTitle;
},
},
});
//组件中使用
<template>
<div class="test">{{NewState}}</div>
</template>
<script>
export default {
computed: {
NewState(){
return this.$store.state.Title
}
},
//其他必要代码
}
</script>
这样就能够在页面上显示Vuex中的Title值了。
除此之外还有一个辅助函数,在本例子中代码改为:
//组件中使用
<template>
<div class="test">{{Title}}</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['Title']),
},
//其他必要代码
}
</script>
3. 对Vuex中State的改变
在上述Vuex例子中已经声明了Mutation,只需要把代码改为:
//组件中使用
<template>
<input type="text" v-model="newTitle">
<div class="test">{{Title}}</div>
<button @click="changeTitle">改变Vuex中的title</button>
</template>
<script>
import { mapState } from 'vuex'
export default {
data(){
return{
newTitle:"",
} },
computed: {
...mapState(['Title']),
},
methods:{
chageTitle(){
this.$store.commit('changeTitle',this.newTitle)
},
},
//其他必要代码
}
</script>
上述代码主要是添加了一个输入框和一个按钮,输入框输入数据后,点击改变按钮,就会触发一个点击事件,这个点击事件中出发函数,函数调用Vuex中Mutation中的方法changeTitle,并传过去了文本框中的值,Mutation中的changeTitle则改变了State中的Title的值,因为组件中获取Vuex的值使用计算属性,因此也会同步到页面。
当然Mutation也有辅助函数,只需在上面修改代码如下:
//组件中使用
<template>
<input type="text" v-model="newTitle">
<div class="test">{{Title}}</div>
<button @click="changeTitles">改变Vuex中的title</button>
</template>
<script>
import { mapState,mapMutations } from 'vuex'
export default {
data(){
return{
newTitle:"",
} },
computed: {
...mapState(['Title']),
},
methods:{
...mapMutations(['changeTitle']),
changeTitles() {
this.changeTitle(this.newTitle)
},
// changeTitle() {
// this.$store.commit('changeTitle', this.newTitle)
// },
},
//其他必要代码
}
</script>
4. Vuex中State的双向绑定
Vuex的State的双向绑定通常用在 form
表单,当然也有其他需要双向绑定的情况。
本小节主要应用技术是Vue计算属性的get
方法和set
方法,对上面例子修改后,代码如下:
export default {
computed: {
newTitle: {
get() {
return this.$store.state.Title;
},
set(value) {
this.$store.commit('changeTitle', value)
}
}
},
}
把提交修改的事件添加到计算属性的set方法中,就可以在值改变的时候触发修改操作,从而改变State的值,这个操作在form表单中是可以行得通的,因为表单一般用v-model绑定数据,而v-model是一个语法糖,它内部拥有input事件,可以触发计算属性的set方法,而不用v-model的时候则不能触发计算属性的set方法,也就无法实现双向绑定,这时候需要使用监听器监听newTitle值的变化,从而触发改变State的Mutation方法,代码如下:
export default {
computed: {
newTitle(){
return this.$store.state.Title;
}
},
watch:{
newTitle(newd,oldd){
this.$store.commit('changeTitle', newd)
}
}
}
因为监听器是在数据发生变化时执行的,所以能够解决非v-model指令不能触发set方法的问题。
5. Vuex中对象属性的深层监听
本小节是对上个小节的一点拓展。
在对Vuex的日常使用中,一般都不会一个属性设置一个值,一般都会进行分组,而模块过大的时候就会启用Vuex的Module,在一个设置配置的Vuex中,一般有如此配置:
//系统设置
config: {
events: true,
calls: false,
messages: false,
notifications: false,
sounds: false,
videoSounds: false
}
我们在组件中使用则会这样用:
<script>
import { mapState } from 'vuex'
export default {
name: 'Drawer',
data() {
return {
docked: false,
position: 'left'
}
},
computed: {
...mapState(['config'])
}
}
</script>
在组件中就会使用v-model="config.events"
来使用,这明显是使用了v-model
的对象赋值,但是计算属性并不会检测内部的变化,从而触发set方法,去提交对State修改的Mutation方法。
使用Vue的监听器设置deep:true
,监听器默认也不会监听内部变化,设置deep
为true
可以监听内部变化,代码如下:
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState(['config']),
},
watch: {
config: {
handler(newValue, oldValue) {
this.$store.commit('changeConfig', newValue)
},
deep: true
},
},
}
</script>
这样就对config
的值进行了深层双向绑定,而不是重复书写这几个配置属性的get
和set
方法
6. Getter
前文提到过,Getter就像计算属性。比如一个列表属性,有时我们只需要其中一条数据我们可以这样做:
export default new Vuex.Store({
state: {
list: [
{ id: 1, text: '我是1'},
{ id: 2, text: '我是2'},
{ id: 3, text: '我是3'}
]
},
getters: {
getone: state => {
return state.list.filter(id=> id===1)
}
}
})
这样调用时,就只会返回第一条数据。
在组件中使用:
computed: {
getOnes () {
return this.$store.getters.getone
}
}
值得注意的是,虽然Vuex中Geeter类似于组件中的计算属性,但是Getter并没有像计算属性那样混入实例(按照计算属性类比的话,Vuex调用Getter需要使用this.$store.state.getone
),但是Getter调用还是要this.$store.getters.getone
。
同样可以使用助手函数:
computed: {
...mapGetters([
'getone'
])
}
7. Action
Action是Vuex中的异步调用解决方法,因为Mutation只能使用同步方法,Action操作的是Mutation中的函数,下面有一个例子:
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
add(state) {
state.count+=1
}
},
actions: {
increment (context) {
context.commit('add')
}
}
})
在使用的时候可以这样操作:
export default {
methods:{
addCount(){
this.$store.dispatch('add')
},
},
//其他必要代码
}
同样可以使用助手函数:
import { mapActions } from 'vuex'
export default {
methods:{
...mapActions(['add']),
},
//其他必要代码
}
如果需要在Action中传递值需要绕一下,代码如下:
import { mapActions } from 'vuex'
export default {
data(){
return{
value:'',
};
},
methods: {
changeTitles() {
this.$store.dispatch('change',value)
},
//其他必要代码
}
//vuex
export default new Vuex.Store({
state: {
Title: "test",
},
mutations: {
changevalue(state, newTitle) {
state.Title = newTitle;
}
},
actions: {
change(context, value) {
context.commit("changeTitle", value);
}
},
});
Action和Mutation类似,都是第二个值才是传入的值,因为Action是对Mutation的操作,所以传值需要多绕一下,但是还是可以达到预期效果的。
8. Module
当你的系统非常庞大时,把不同模块的State和Mutation写在一起会非常的乱,因此Vuex还提供了模块化。
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态
上面代码是引用Vuex官方文档的代码,只需要在引用方法和操作时加上模块名,就可以正确读取到相应模块的值和方法。
操作方法 | 非模块化 | 模块化 |
---|---|---|
获取State | this.$store.state.value; |
this.$store.state.[模块名].value |
mapState | ...mapState(['value]) |
...mapState(['[模块名]/value']) |
Mutation | this.$store.commit('event') |
this.$store.[模块名].commit('event') |
mapMutation | ...mapMutation(['event]) |
...mapMutation(['[模块名]/event']) |
Action和Getter类似。
使用webpack批量导入Modules
require.context
是webpack的一个用来管理依赖的一个函数,使用它可以批量导入,详细查看文档
https://webpack.docschina.org/guides/dependency-management/#require-context
具体使用:
import Vue from "vue";
import Vuex from "vuex";
import getters from "./getters";
Vue.use(Vuex);
// 引入modules下的所有文件
const modulesFiles = require.context("./modules", false, /\.js$/);
const modules = modulesFiles.keys().reduce((modules, path) => {
// ./app.js => app
const name = path.replace(/^\.\/(.*)\.\w+$/, "$1");
// 如果文件是空的则,下面这句取不出来结果
const value = modulesFiles(path);
modules[name] = value.default;
return modules;
}, {});
const store = new Vuex.Store({
modules,
getters
});
export default store;