1.前言
上篇文章介绍了vuex的核心
2.Vuex如何使用
1. 安装vuex模块
npm install vuex --save
2. 作为插件使用
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
3. 定义容器
let store = new Vuex.Store()
4. 注入根实例
import store from "./store"
new Vue({
el: '#app',
store,
components: { App },
template: '<App/>'
})
3.State
store类似容器,包含应用的大部分状态(相当于组件的data数据),store的有几个特点如下
:
1. 一个页面只能有一个store
2. 状态存储是响应式的
3. 不能直接改变 store 中的状态,唯一途径显式地提交mutations
下面先来创建一个store容器,定义一个state对象,创建一个状态num
import vue from "vue"
import vuex from "vuex"
vue.use(vuex);
let stroe = new vuex.Store({
state: {
num: 100
}
});
export default stroe;
因为stroe 被注入了根实例,所以任何的组件都可以通过this.$store.state.num获取到这个num状态,还是用上篇文章的例子把App,Parent,Child,GrandSon组件都应用上这个状态,我这里只列出GrandSon的组件的代码,其他都一样!
<template>
<div>
<h2>GrandSon组件的number值:{{ num }}</h2>
<button>点击我改变number值</button>
<button>点击我改变number值</button>
</div>
</template>
<script>
export default {
name: "GrandSon",
computed: {
num() {
return this.$store.state.num;
},
}
};
</script>
每个组件都可以获取到这个状态,看下效果:
因为这个只有一个状态,当有多个状态(num,num1,num2)的话,每次都要将这些状态都声明为计算属性并且通过this.$store.state.xxx会有些重复和麻烦,所以就有个mapState 辅助函数能帮助简化写法,这里有多种写法!
1.第一种函数(箭头函数或者普通函数)写法
<template>
<div>
<h2>GrandSon组件的number值:{{ num }}</h2>
<button>点击我改变number值</button>
<button>点击我改变number值</button>
</div>
</template>
<script>
export default {
name: "GrandSon",
computed:mapState({
//箭头函数写法
//num: state => state.num,
// num1:state => state.num1,
// num2:state => state.num2,
//普通函数写法
num(state){
return state.num
},
num1(state){
return state.num1
},
num2(state){
return state.num2
},
}),
};
</script>
2.第二种字符串写法
<template>
<div>
<h2>GrandSon组件的number值:{{ num }}</h2>
<button>点击我改变number值</button>
<button>点击我改变number值</button>
</div>
</template>
<script>
export default {
name: "GrandSon",
computed:mapState({
num:"num",
num1:"num1",
num2:"num2"
}),
};
</script>
3.第三种数组写法(如果计算属性名称和状态名称相同)
<template>
<div>
<h2>GrandSon组件的number值:{{ num }}</h2>
<button>点击我改变number值</button>
<button>点击我改变number值</button>
</div>
</template>
<script>
export default {
name: "GrandSon",
computed:mapState(["num","num1","num2"])
};
</script>
如果有其他的计算属性(局部计算属性)的话,应该使用es6的对象展开运算符将多个对象合并为一个
<template>
<div>
<h2>GrandSon组件的number值:{{ num }}</h2>
<button>点击我改变number值</button>
<button>点击我改变number值</button>
</div>
</template>
<script>
export default {
name: "GrandSon",
computed:{
//组件局部计算属性test
test(){},
...mapState(["num","num1","num2"])
}
};
</script>
4.Mutation
mutation是唯一修改状态的事件回调函数,每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。注意:更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。下面来写下写两个数字加减的mutation回调函数,回调函数可以接受两个参数,第一个参数是state状态,第二个参数是 mutation 的 载荷就是外面store.commit 传入额外的参数
//store index.js
import vue from "vue"
import vuex from "vuex"
vue.use(vuex);
let stroe = new vuex.Store({
state: {
num: 100
},
mutations: {
addNumber(state, payload) {
state.num += payload.n;
},
substractNumber(state, payload) {
state.num -= payload.n;
}
}
});
export default stroe;
//GrandSon.vue
<template>
<div>
<h2>GrandSon组件的number值:{{ num }}</h2>
<button @click="addNumber">点击我改变number值</button>
<button @click="substractNumber">点击我改变number值</button>
</div>
</template>
<script>
export default {
name: "GrandSon",
computed:mapState(["num"]),
methods: {
addNumber() {
//两种写法
//this.$store.commit("addNumber", {n: 5});
this.$store.commit({
type:"addNumber",
n:5
});
},
substractNumber() {
//两种写法
//this.$store.commit("substractNumber", {n: 10});
this.$store.commit({
type:"substractNumber",
n:10
});
}
};
</script>
只要触发点击事件,就会提交mutation(修改改状态的事件回调函数),状态发了改变后,那么只要用到这个状态的组件都自己会响应式发生变化
5.Getter
你可以理解它是一个状态计算属性,好比组件里面的computed属性,所以getter 的返回值也会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算,说白了就是对store 中的 state 做了一层封装,还是拿上面的例子来改写,GrandSon组件里面加法运算当大于等于120就等于120(不能再加上去了),我们来封装一下getter ,getter 可以接受两个参数,第一个是状态state,第二个参数是其他getter !
// store index.js
import vue from "vue"
import vuex from "vuex"
vue.use(vuex);
let stroe = new vuex.Store({
state: {
num: 100
},
getters: {
filterNum:(state,getter) =>{
return state.num > 120 ? 120 : state.num
}
},
mutations: {
addNumber(state, payload) {
state.num += payload.n;
},
substractNumber(state, payload) {
state.num -= payload.n;
}
}
});
export default stroe;
//GrandSon.vue
<template>
<div>
<h2>GrandSon组件的number值:{{ computedNum}}</h2>
<button @click="addNumber">点击我改变number值</button>
<button @click="substractNumber">点击我改变number值</button>
</div>
</template>
<script>
export default {
name: "GrandSon",
computed:{
computedNum() {
return this.$store.getters.filterNum;
},
...mapState(["num"]),
}
methods: {
addNumber() {
//两种写法
//this.$store.commit("addNumber", {n: 5});
this.$store.commit({
type:"addNumber",
n:5
});
},
substractNumber() {
//两种写法
//this.$store.commit("substractNumber", {n: 10});
this.$store.commit({
type:"substractNumber",
n:10
});
}
};
</script>
看下下面的效果:
当然如果有多个getter,可以使用mapGetters 辅助函数来简化写法:
1. 如果computed计算属性名和getters里面的属性名一样!
//GrandSon.vue
<template>
<div>
<h2>GrandSon组件的number值:{{ filterNum}}</h2>
<button @click="addNumber">点击我改变number值</button>
<button @click="substractNumber">点击我改变number值</button>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
export default {
name: "GrandSon",
computed:{
...mapGetters(["filterNum"]),
...mapState(["num"]),
}
methods: {
addNumber() {
//两种写法
//this.$store.commit("addNumber", {n: 5});
this.$store.commit({
type:"addNumber",
n:5
});
},
substractNumber() {
//两种写法
//this.$store.commit("substractNumber", {n: 10});
this.$store.commit({
type:"substractNumber",
n:10
});
}
};
</script>
2. computed计算属性名和getters里面的属性名不一样!
//GrandSon.vue
<template>
<div>
<h2>GrandSon组件的number值:{{ computedNum}}</h2>
<button @click="addNumber">点击我改变number值</button>
<button @click="substractNumber">点击我改变number值</button>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
export default {
name: "GrandSon",
computed:{
...mapGetters({
computedNum: "filterNum"
}),
...mapState(["num"]),
}
methods: {
addNumber() {
//两种写法
//this.$store.commit("addNumber", {n: 5});
this.$store.commit({
type:"addNumber",
n:5
});
},
substractNumber() {
//两种写法
//this.$store.commit("substractNumber", {n: 10});
this.$store.commit({
type:"substractNumber",
n:10
});
}
};
</script>
6.Vue.js devtools
这是goolgle的一个调试vuex的插件,可以很清楚的看到组件之间的层级关系,还有各个组件的局部数据
还可以很清楚的看到各个状态的变化,还可以通过时间旅行回到过去的任意状态!并且可以看到这些状态都是可预测的,逻辑非常的清晰,调试起来非常的方便!
7.Action
Action 和Mutation非常的相似,只不过Mutation是同步的,Action是异步的,如果你提交一个异步的Mutation就不可以预测了,我们可以通过上面的Vue.js devtools来做个例子!把mutations的addNumber函数该成延迟5秒再执行
上图可以看到我迅速的点击了3次,通过插件时间旅行发现状态都是100,这样就有问题的,这种异步情况就是不可预测的,那么来改下,把异步操作放到Action里面
// store index.js
import vue from "vue"
import vuex from "vuex"
vue.use(vuex);
let stroe = new vuex.Store({
state: {
num: 100
},
getters: {
filterNum:(state,getter) =>{
return state.num > 120 ? 120 : state.num
}
},
mutations: {
addNumber(state, payload) {
state.num += payload.n;
},
substractNumber(state, payload) {
state.num -= payload.n;
}
},
actions: {
addAction(context){
console.log(context);
setTimeout(()=>{
context.commit("addNumber");
},5000)
}
});
export default stroe;
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,看到里面有commit,所以Action 和Mutation区别就是Action提交的是Mutation,而不是直接变更状态,并且Action 可以包含任意异步操作而Mutation只能是同步的
那么组件也需要分发 Action用到store.dispatch 方法触发,来修改下GrandSon组件如下:
//GrandSon.vue
<template>
<div>
<h2>GrandSon组件的number值:{{ computedNum}}</h2>
<button @click="addNumber">点击我改变number值</button>
<button @click="substractNumber">点击我改变number值</button>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
export default {
name: "GrandSon",
computed:{
...mapGetters({
computedNum: "filterNum"
}),
...mapState(["num"]),
}
methods: {
addNumber() {
this.$store.dispatch("addAction");
},
substractNumber() {
//两种写法
//this.$store.commit("substractNumber", {n: 10});
this.$store.commit({
type:"substractNumber",
n:10
});
}
};
</script>
既然context对象里面有dispatch属性,那么如果有多个异步函数(a,b,c),先执行异步a函数,在异步b函数里面可以dispatch a函数,异步c函数可以dispatch 函数,dispatch 函数也可以和commit一样接受一个type属性和参数的对象,下面来模拟下:
// store index.js
import vue from "vue"
import vuex from "vuex"
vue.use(vuex);
let stroe = new vuex.Store({
state: {
num: 100
},
getters: {
filterNum:(state,getter) =>{
return state.num > 120 ? 120 : state.num
}
},
mutations: {
addNumber(state, payload) {
state.num += payload.n;
},
substractNumber(state, payload) {
state.num -= payload.n;
}
},
actions: {
addActionA(context, payload) {
setTimeout(() => {
context.commit({
type: "addNumber",
n:payload.n
});
}, 1000)
},
addActionB(context, payload) {
setTimeout(() => {
context.dispatch({
type: "addActionA",
n:payload.n
});
}, 1000)
},
addActionC(context, payload) {
setTimeout(() => {
context.dispatch({
type: "addActionB",
n:payload.n
});
}, 1000)
}
});
export default stroe;
GrandSon组件需要修改下分下事件的名称addActionC
//GrandSon.vue
<template>
<div>
<h2>GrandSon组件的number值:{{ computedNum}}</h2>
<button @click="addNumber">点击我改变number值</button>
<button @click="substractNumber">点击我改变number值</button>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
export default {
name: "GrandSon",
computed:{
...mapGetters({
computedNum: "filterNum"
}),
...mapState(["num"]),
}
methods: {
addNumber() {
this.$store.dispatch({
type:"addActionC",
n:5
});
},
substractNumber() {
//两种写法
//this.$store.commit("substractNumber", {n: 10});
this.$store.commit({
type:"substractNumber",
n:10
});
}
};
</script>
点击加法事件,3秒之后会提交mutations改变状态,看下效果:
context既然是对象,你也可以利用es6的结构赋值来简写
actions: {
addActionA({commit}, payload) {
setTimeout(() => {
commit({
type: "addNumber",
n:payload.n
});
}, 1000)
},
addActionB({dispatch}, payload) {
setTimeout(() => {
dispatch({
type: "addActionA",
n:payload.n
});
}, 1000)
},
addActionC({dispatch}, payload) {
setTimeout(() => {
dispatch({
type: "addActionB",
n:payload.n
});
}, 1000)
}
}
如果你觉得写this.$store.dispatch麻烦,也可以用mapActions来简写,下面接着来修改下GrandSon组件!
//GrandSon.vue
<template>
<div>
<h2>GrandSon组件的number值:{{ computedNum}}</h2>
<button @click="addNumber({n:5})">点击我改变number值</button>
<button @click="substractNumber">点击我改变number值</button>
</div>
</template>
<script>
import { mapState, mapGetters } from "vuex";
export default {
name: "GrandSon",
computed:{
...mapGetters({
computedNum: "filterNum"
}),
...mapState(["num"]),
}
methods: {
//如果事件方法名和分发事件名相同,可以这么写
//...mapActions(["addActionC"]),
...mapActions({
//这里传的参数{n:5}应该写成上面的addNumber函数参数
//@click="addNumber({n:5})"
addNumber: "addActionC",
}),
substractNumber() {
//两种写法
//this.$store.commit("substractNumber", {n: 10});
this.$store.commit({
type:"substractNumber",
n:10
});
}
};
</script>
8.Module
Module可以将 store 分割成不同的模块,上面的例子演示的只有一个模块,可以把它分离出来 ,看下面代码:
import vue from "vue"
import vuex from "vuex"
vue.use(vuex);
let moduleA={
state: {
num: 100,
num1: 200,
num2: 300
},
getters: {
filterNum: (state) => {
return state.num > 120 ? 120 : state.num
}
},
mutations: {
addNumber(state, payload) {
state.num += payload.n;
},
substractNumber(state, payload) {
state.num -= payload.n;
}
},
actions: {
addActionA({commit}, payload) {
setTimeout(() => {
commit({
type: "addNumber",
n:payload.n
});
}, 1000)
},
addActionB({dispatch}, payload) {
setTimeout(() => {
dispatch({
type: "addActionA",
n:payload.n
});
}, 1000)
},
addActionC({dispatch}, payload) {
setTimeout(() => {
dispatch({
type: "addActionB",
n:payload.n
});
}, 1000)
}
}
}
let stroe = new vuex.Store({
modules:{
//moduleA:moduleA 也是这样写,下面是es6的简写
moduleA
}
});
export default stroe;
既然现在已经分了模块,那么组件要取到模块的状态,应该怎么取呢,先把this.$store打印出来看下:
computed: {
num(){
console.log(this.$store);
}
}
打印出来之后,应该知道怎么写了
computed: {
num(){
return this.$store.state.moduleA.num;
}
}