Vue全家桶系列之Vuex(二)

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;
    }
  }
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值