Vuex Demo 入门解析

1,982 阅读4分钟

Vuex的作用

  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
  • 使用Vuex后组件仍然保有局部状态,使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。

Vuex基本结构

  • state 用来数据共享数据存储
  • mutation 用来注册改变数据状态
  • getters 用来对共享数据进行过滤操作
  • action 解决异步改变共享数据
//src/store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  //变量data
  state: {},
  //类似于计算属性computed
  getters: {},
  //方法methods
  mutations: {},
  //异步操作
  actions: {},
  //模块
  modules: {}
});
//src/main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

Vue.config.productionTip = false

new Vue({
  router,
  // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
  store,
  render: h => h(App)
}).$mount('#app')

State

Vuex 使用单一状态树,即用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。

//src/store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  //变量data
  state: {
    count: 100,
  },
  //方法methods
  mutations: {},
  //异步操作
  actions: {},
  //模块
  modules: {},
  //类似于计算属性computed
  getters: {}
});
<!--src/views/Home.vue-->
<template>
  <div class="home">
	<!--在此使用$store.state.count使用之前声明的count-->
    count:{{ $store.state.count }}
    <br>
    <!--在此通过计算属性doubleCount得到count-->
    doubleCount:{{doubleCount}}
  </div>
</template>

<script>
// @ is an alias to /src

export default {
  name: "Home",
  computed: {
    doubleCount(){
      return this.$store.state.count * 2
    }
  },
  components: {},
};
</script>

在devtool中可以查看到在state声明的count

1.png

Getter

Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性),getter类似于计算属性, 其返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算

Getter 接受 state 作为其第一个参数

//src/store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  //变量data
  state: {
    count: 100,
    people:[
      {name:'小A', age:8, address:'北京'},
      {name:'小B', age:18, address:'上海'},
      {name:'小C', age:28, address:'广州'}
    ]
  },
  //类似于计算属性computed
  getters: {
    getPeople:state=>{
      return state.people.filter(n=>n.age>10)
    }
  },
  //方法methods
  mutations: {},
  //异步操作
  actions: {},
  //模块
  modules: {}
});
<!--src/views/Home.vue-->
<template>
  <div class="about">
    <h1>{{$store.getters.getPeople}}</h1>
  </div>
</template>

2.png

Getter 也可以接受其他 getter 作为第二个参数

//src/store/index.js
  getters: {
    getPeople:state=>{
      return state.people.filter(n=>n.age>10)
    },
    getPeopleLength:(state,getters)=>{
      //直接调用上面的getPeople方法并取得其长度
      return getters.getPeople.length
    }
  }
<!--src/views/Home.vue-->
<template>
  <div class="about">
    <h1>getPeople:{{$store.getters.getPeople}}</h1>
    <h3>getPeopleLength:{{$store.getters.getPeopleLength}}</h3>
  </div>
</template>

3.png

通过让 getter 返回一个函数,来实现给 getter 传参

//src/store/index.js
getters: {
    getPeople: (state) => {
      return state.people.filter((n) => n.age > 10);
    },
    getPeopleLength: (state, getters) => {
      //直接调用上面的getPeople方法并取得其长度
      return getters.getPeople.length;
    },
    getPeopleByage: (state) => (age) => {
      return state.people.find((n) => n.age === age);
    },
    /*等同于
    getPeopleByage:function(state){
      return function(age){
        return state.people.find(function(n){
          return n.age===age
        })
      }
    }*/
  }
<!--src/views/Home.vue-->
<template>
  <div class="about">
    <h1>getPeople:{{$store.getters.getPeople}}</h1>
    <h3>getPeopleLength:{{$store.getters.getPeopleLength}}</h3>
    <h5>getPeopleByage:{{$store.getters.getPeopleByage(28)}}</h5>
  </div>
</template>

4.png

Mutation

更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type) 和 一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数

虽然直接对state进行更改也可以得到同样的效果,但是devtool无法监听到state的变化,这将会给项目的维护带来困难,所以对状态更改还是需要使用commit方法

//src/store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  //变量data
  state: {
    count: 100,
    people: [
      { name: "小A", age: 8, address: "北京" },
      { name: "小B", age: 18, address: "上海" },
      { name: "小C", age: 28, address: "广州" },
    ],
  },
  //类似于计算属性computed
  getters: {
    getPeople: (state) => {
      return state.people.filter((n) => n.age > 10);
    },
    getPeopleLength: (state, getters) => {
      //直接调用上面的getPeople方法并取得其长度
      return getters.getPeople.length;
    },
    getPeopleByage: (state) => (age) => {
      return state.people.find((n) => n.age === age);
    },
  },
  //方法methods
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    }
  },
  //异步操作
  actions: {},
  //模块
  modules: {},
});
<!--src/views/Home.vue-->
<template>
  <div class="home">
    count:{{ $store.state.count }}
    <br />
    doubleCount:{{ doubleCount }}
    <button @click="add">+</button>
    <button @click="sub">-</button>
  </div>
</template>

<script>
// @ is an alias to /src

export default {
  name: "Home",
  computed: {
    doubleCount() {
      return this.$store.state.count * 2;
    }
  },
  methods: {
    add(){
      this.$store.commit('increment')
    },
    sub(){
      this.$store.commit('decrement')
    }
  },
  components: {},
};
</script>

5.png

你可以向 store.commit 传入额外的参数,即 mutation 的 载荷(payload)

//src/store/index.js
mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    },
    mul(state,payload){
      state.count *= payload.amount
    }
  }
<!--src/views/Home.vue-->
<template>
  <div class="home">
    count:{{ $store.state.count }}
    <br />
    doubleCount:{{ doubleCount }}
    <button @click="add">+</button>
    <button @click="sub">-</button>
    <button @click="mul">*10</button>
  </div>
</template>

<script>
// @ is an alias to /src

export default {
  name: "Home",
  computed: {
    doubleCount() {
      return this.$store.state.count * 2;
    },
  },
  methods: {
    add() {
      this.$store.commit("increment");
    },
    sub() {
      this.$store.commit("decrement");
    },
    mul() {
      this.$store.commit({
        type: "mul",
        amount: 10,
      });
    },
  },
  components: {},
};
</script>

6.png

Action

Action 类似于 mutation,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态
  • Action 可以包含任意异步操作

理解了mutation之后理解action也相对简单,action用于处理异步操作,与mutation相比,action通过$store.dispatch('xxx')触发而不是store.commit('xxx'),触发之后会交给对应的action处理异步操作,然后再通过commit交予mutation实现数据更改。流程可简单理解为`store.dispatch => action => $store.commit => mutation`

<!--src/views/Home.vue-->
<template>
  <div class="home">
    count:{{ $store.state.count }}
    <br />
    doubleCount:{{ doubleCount }}
    <button @click="add">+</button>
    <button @click="sub">-</button>
    <button @click="mul">*10</button>
    <button @click="div">/10</button>
  </div>
</template>

<script>
// @ is an alias to /src

export default {
  name: "Home",
  computed: {
    doubleCount() {
      return this.$store.state.count * 2;
    },
  },
  methods: {
    add() {
      this.$store.commit("increment");
    },
    sub() {
      this.$store.commit("decrement");
    },
    mul() {
      this.$store.commit({
        type: "mul",
        amount: 10,
      });
    },
    div() {
      this.$store.dispatch("division");
    },
  },
  components: {},
};
</script>
//src/store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  //变量data
  state: {
    count: 100,
    people: [
      { name: "小A", age: 8, address: "北京" },
      { name: "小B", age: 18, address: "上海" },
      { name: "小C", age: 28, address: "广州" },
    ],
  },
  //类似于计算属性computed
  getters: {
    getPeople: (state) => {
      return state.people.filter((n) => n.age > 10);
    },
    getPeopleLength: (state, getters) => {
      //直接调用上面的getPeople方法并取得其长度
      return getters.getPeople.length;
    },
    getPeopleByage: (state) => (age) => {
      return state.people.find((n) => n.age === age);
    },
  },
  //方法methods
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    },
    mul(state, payload) {
      state.count *= payload.amount;
    },
    div(state, payload) {
      state.count /= payload.amount;
    },
  },
  //异步操作
  actions: {
    division(context) {
      context.commit({
        type: "div",
        amount: 10,
      });
    },
  },
  //模块
  modules: {},
});

7.png

Module

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块. 要调用定义的模块内的属性可使用$store.state.模块.属性

//src/store/index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

const moduleA = {
  state: {
    count: 11111111111111,
  },
  mutations: {},
  actions: {},
  getters: {
    //对于模块内部的 getter,根节点状态会作为第三个参数暴露出来
    sumWithRootCount(state, getters, rootState) {
      return state.count + rootState.count;
    }
  }
};

export default new Vuex.Store({
  //变量data
  state: {
    count: 100,
    people: [
      { name: "小A", age: 8, address: "北京" },
      { name: "小B", age: 18, address: "上海" },
      { name: "小C", age: 28, address: "广州" },
    ],
  },
  //类似于计算属性computed
  getters: {
    getPeople: (state) => {
      return state.people.filter((n) => n.age > 10);
    },
    getPeopleLength: (state, getters) => {
      //直接调用上面的getPeople方法并取得其长度
      return getters.getPeople.length;
    },
    getPeopleByage: (state) => (age) => {
      return state.people.find((n) => n.age === age);
    },
  },
  //方法methods
  mutations: {
    increment(state) {
      state.count++;
    },
    decrement(state) {
      state.count--;
    },
    mul(state, payload) {
      state.count *= payload.amount;
    },
    div(state, payload) {
      state.count /= payload.amount;
    },
  },
  //异步操作
  actions: {
    division(context) {
      context.commit({
        type: "div",
        amount: 10,
      });
    },
  },
  //模块
  modules: {
    moduleA
  },
})
<!--src/views/Home.vue-->
<template>
  <div class="home">
    count:{{ $store.state.count }}
    <br/>
    doubleCount:{{ doubleCount }}
    <button @click="add">+</button>
    <button @click="sub">-</button>
    <button @click="mul">*10</button>
    <button @click="div">/10</button>
    <br>
    {{$store.state.moduleA.count}}
    {{$store.state.sumWithRootCount}}
  </div>
</template>

<script>
// @ is an alias to /src

export default {
  name: "Home",
  computed: {
    doubleCount() {
      return this.$store.state.count * 2;
    }
  },
  methods: {
    add() {
      this.$store.commit("increment");
    },
    sub() {
      this.$store.commit("decrement");
    },
    mul() {
      this.$store.commit({
        type: "mul",
        amount: 10,
      });
    },
    div() {
      this.$store.dispatch("division");
    },
  },
  components: {},
};
</script>

8.png

关于Vuex还有一些功能未讲述,可进入Vuex官网继续学习