网站首页 » 前端开发 » Vue » Vue2 vuex 状态管理详解
上一篇:
下一篇:

Vue2 vuex 状态管理详解

前言

在 Vue 中有一个统一管理状态的地方,没错它就是将要出场的 vuex ,vuex 是用来管理状态的,状态是什么?说得接地气点就是一些变量,这些变量可以通过特定的方式在全局中调用,让不同组件之间的数据通信变得如鱼得水。

vuex 状态管理之旅

vuex 是什么

官方有这么一小段话:

官方定义

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

上面这一行文字包括了两个方面的内容,状态管理的定义及原理。

vuex 工作原理

如果不是很明白上面的后半句话,我们不如打个比方,以相亲为例:

假设你们村里的少男少女都很腼腆,害羞得不行,你也不例外。正因为这样,你们村出一个男女通的媒婆,手里掌握着村的所有未婚男女资源(状态)。有一天,男一号(你)看上了一位貌美如花的姑娘(G),但你不敢也不能直接去表白,不过你可以以某种方式(方法)告诉媒婆:你喜欢上了 G,让她帮你传情。媒婆收到你的请求后,用她自己独有的方式(方法)来告诉你的女神 G,不管女神 G 喜欢不喜欢你,此时她心里肯定是在翻腾的,身体内的某种激素肯定会有所波动(变量)。

献上官方说明图一张:

Vue vuex 状态管理详解

vuex 知识点

  • store:相当于一个容器,;它是响应式的在全局都可以使用它;一个应用里只能定义一个 store 容器。
  • state:这里对象里面放了各种状态(变量)
  • getters:在组件内部获取 store 中状态的函数
  • mutations:唯一用来修改状态的回调函数
  • actions:包含异步操作,提交 mutations 来修改状态
  • module:将 store 分割成不同的模块

vuex 简单实战

主要文件结构
├── index.html
├── assets
├── main.js
├── router
│  └── index.js
├── store
│  └── index.js
├── App.vue  
└── components
   └── demo.vue

下面就是主要的代码:

在使用 vuex 之前我们,我们应该安装 vuex 。

npm install vuex --save

安装完后我们就可以在页面中使用它 了,首先我们要定义它,新建 store/index.js 文件,内容如下:

import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
// 定义一个状态管理容器
let store = new Vuex.Store({
  state: {
    count: 10086
  },
  // state 中定义的所有状态,都必需也只能通过 mutations 中的方法来进行修改,在其它组件中通过调用此方法来间接地修改 state 中的状态
  mutations: {
    add(state) {
      state.count++;
    },
    del(state,params) {
      state.count -= params.step;
    }
  }
});
export default store

接着我们就要在 main.js 中引入它:

main.js
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'

Vue.config.productionTip = false

/* eslint-disable no-new */
let vm = new Vue({
  el: '#app',
  router,
  store,
  components: {
    App
  },
  template: '<App/>'
})
提示:

import store form ‘./store’ 中的 import 后面的变量要小写,不然 store 不会挂载到 Vue 上的,导致你无法在实例中通过 this.store 来获取 store 状态管理容器

最后我们就可以在其它组件中使用定义好的状态 count 了:

Demo.vue
<template>
 <div>
   <div>
     <button class="btn btn-primary" @click="delHandler">-</button>
     {{num}}
     <button class="btn btn-primary"  @click="addHandler">+</button>
   </div>
 </div>
</template>
<script>
export default {
  name: "Demo",
  computed: {
    num() {
      return this.$store.state.count;
    }
  },
  methods: {
    delHandler() {
      // 操作 mutations 中的对应方法(del)来修改 state 中的状态 count
      this.$store.commit("del",{
          step:6
      });
    },
    addHandler() {
      // 操作 mutations 中的对应方法(add)来修改 state 中的状态 count
      this.$store.commit("add");
    }
  }
};
</script>

在其它组件中通过 $store.state.count 来获取状态,通过 this.$stroe.commit() 方法来提交对应的方法名告诉 store 要执行 mutations 中的哪一个方法来修改状态。commit() 可以接收第二个参数,表示要传给 add() 方法的参数,参数可以是一个单纯的数字,字符串或者其它基本类型,也可以是一个对象。commit() 还有一种写法:

......
delHandler() {
  // 操作 mutations 中的对应方法(del)来修改 state 中的状态 count
  this.$store.commit({
    type: "del",
    step: 6
  });
},
......

actions 异步操作

如果我们在点击减按钮时要先去请求主个接口,请接口返回来后再去变更状态,此时我们就需要用到状态管理中的 actiions 了。

store/index.js(actiions 版本)
Vue.use(Vuex);
// 定义一个状态管理容器
let store = new Vuex.Store({
  state: {
    count: 10086
  },
  // state 中定义的所有状态,都必需也只能通过 mutations 中的方法来进行修改,在其它组件中通过调用此方法来间接地修改 state 中的状态
  mutations: {
    add(state) {
      state.count++;
    },
    del(state, params) {
      state.count -= params.step;
    }
  },
  actions: {
    fetchThenDel(context, params) {
      setTimeout(() => {
        context.commit("del", params)
      }, 1000);
    }
  }
});
export default store

通过actions 的 commit() 来提交 mutations。然后在其它组件中通过 dispatch() 方法来触发一个 actions 动作。

Demo.vue (actions 版本)
<template>
 <div>
   <div>
     <button class="btn btn-primary" @click="delHandler">-</button>
     {{num}}
     <button class="btn btn-primary"  @click="addHandler">+</button>
   </div>
 </div>
</template>
<script>
export default {
  name: "Demo",
  computed: {
    num() {
      return this.$store.state.count;
    }
  },
  methods: {
    delHandler() {
      // 操作 mutations 中的对应方法(del)来修改 state 中的状态 count
      this.$store.dispatch("fetchThenDel", {
        step: 6
      });
    },
    addHandler() {
      // 操作 mutations 中的对应方法(add)来修改 state 中的状态 count
      this.$store.commit("add");
    }
  }
};
</script>

这样下来,感觉棒极了。但是官方描述中有这么一句话:改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。

很不错的是,我直接在组件中你下面这样写

delHandler() {
    this.$store.state--;
},

而不通过提交 mutation,也可以修改状态值,并且也不会报错,这是什么原因???,当我再当下仔细的阅读时发现还有这么一段话:

重要

再次强调,我们通过提交 mutation 的方式,而非直接改变 store.state.count,是因为我们想要更明确地追踪到状态的变化。这个简单的约定能够让你的意图更加明显,这样你在阅读代码的时候能更容易地解读应用内部的状态改变。此外,这样也让我们有机会去实现一些能记录每次状态改变,保存状态快照的调试工具。有了它,我们甚至可以实现如时间穿梭般的调试体验。

在这里总结下修改状态值套路,官方也并没有禁止上面那种简单粗暴的写法,而是让你可以为所欲为,官方只是建议通过提交 mutaion 这种方法来修改状态值。但是我们在写代码的时候最好就按官方的套路来。

getters 属性

不过有时候我们在一个组件中两个地方都使用到了这个 count 状态,但是需要给其中一个添加一个约束条件,比如大于10088 就不能再往上加了。这时我们就可以使用 getters 属性实现这个效果,这个 getters 属性相当于组件中的计算属性。

store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
// 定义一个状态管理容器
let store = new Vuex.Store({
  state: {
    count: 10086
  },
  mutations: {
    add(state) {
      state.count++;
    },
    del(state, params) {
      state.count -= params.step;
    }
  },
  getters: { // 类似于组件中的计算属性
    fillterCount(state) {
      return state.count > 10088 ? 10088 : state.count;
    }

  },
  actions: {
    fetchThenDel(context, params) {
      setTimeout(() => {
        context.commit("del", params)
      }, 1000);
    }
  }
});
export default store

在 Demo.vue 组件中这样调用

Demo.vue
<template>
 <div>
   <div>
     <button class="btn btn-primary" @click="delHandler">-</button>
     {{num}}-{{filterNum}}
     <button class="btn btn-primary"  @click="addHandler">+</button>
   </div>
 </div>
</template>
<script>
export default {
  name: "Demo",
  computed: {
    num() {
      return this.$store.state.count;
    },
    filterNum() {
      return this.$store.getters.fillterCount;
    }
  },
  methods: {
    delHandler() {
      // 操作 mutations 中的对应方法(del)来修改 state 中的状态 count
      this.$store.dispatch("fetchThenDel", {
        step: 6
      });
    },
    addHandler() {
      // 操作 mutations 中的对应方法(add)来修改 state 中的状态 count
      this.$store.commit("add");
    }
  }
};
</script>

其实像上面那个条件的判断我们完全可以直接在组件中的计算属性中进行过滤,为什么还得那样做呢?

......
computed: {
 num() {
   return this.$store.state.count > 10088 ? 10088 : this.$store.state.count;
 },
 filterNum() {
   return this.$store.getters.fillterCount;
 }
},
......

还是那句话:用别人的东西,我们就应该按别人的套路出牌。

辅助函数

辅助函数就是为了简单我们使用状态管理的操作,让代码看起来更加简洁。state、getters、actions、mutations 都会有自己相对的一个用于简化调用状态的函数 mapState、 mapGetters、mapActions、mapMutations。在使用它们之前,我们必需得先从 vuex 中引入它们。

import { mapState } from "vuex";

在这里我们使用了 ES6 中的解构赋值来获取相对应的函数。

下面我们以 mapState 为例,给 mapState  传一个对像或者一个数组两种方式都可以。

  computed: mapState({
  // 写法一
  count: state => state.count
  // 写法二
  count: "count"
  // 写法三
  count(state) {
      return state.count + 100;
  }
  }),
  // 数组方式(数组的话就只有一种写法)
  computed: mapState(["count"]),

传对象的话,有三种写法,但写法二,在把状态管理分割成模块后就不再适用了,只能通过写法一和写法三的方式来获取。

其它三个(mapGetters、mapActions、mapMutations)用法是一样的。照猫画虎就好。

下面我们就把这这些简写用到组件中

实战例子
<template>
 <div>
   <div>
     <button class="btn btn-primary" @click="delHandler({step:6})">-</button>
     {{count}}-{{fillterCount}}
     <button class="btn btn-primary"  @click="addHandler">+</button>
   </div>
 </div>
</template>
<script>
import { mapState, mapGetters, mapActions, mapMutations } from "vuex";
export default {
  name: "Demo",
  // 混合写法(包括没引用 state 状态管中的状态的计算属性)
  computed: {
    other() {
      return 1;
    },
    ...mapState({ count: "count" }),
    ...mapGetters({ fillterCount: "fillterCount" })
  },
  methods: {
    ...mapActions({
      delHandler: "fetchThenDel"
    }),
    ...mapMutations({
      addHandler: "add"
    })
  }
};
</script>

这里有一点需要注意的,那就是我们在点击减按钮的时候我们需要传一个对象作为参数的,所以我们需要修改下传参的方式,把参数放到模板中。

<button class="btn btn-primary" @click="delHandler({step:6})">-</button>

状态模块化

试想一下,一个应用中如果用到很多个状态,那场面真的是不堪入目,乱成一团,所有的状态都交织在一起,这样管理起来就非常地困难了,找个地方都得找大半天,不过,vuex 为我们提供了一个状态模块化的管理方式,我们可以把不同功能的状态放到单独的目录下的一个 index.js 文件中,以后如需再添加状态的话,也单独新建一个目录,把状态代码放到目录下的 index.js 文件就可以。

下面我们来看个例子:

store/module/countModule
let countModule = {
  state: {
    count: 10086
  },
  // state 中定义的所有状态,都必需也只能通过 mutations 中的方法来进行修改,在其它组件中通过调用此方法来间接地修改 state 中的状态
  mutations: {
    add(state) {
      state.count++;
    },
    del(state, params) {
      state.count -= params.step;
    }
  },
  getters: { // 类似于组件中的计算属性
    fillterCount(state) {
      return state.count > 10088 ? 10088 : state.count;
    }
  },
  actions: {
    fetchThenDel(context, params) {
      setTimeout(() => {
        context.commit("del", params)
      }, 1000);
    }
  }
}
export default countModule

现在我们只需要在 store 目录下的 index.js 文件引入这个模块就可以。

store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import countModule from './module/countModule'
Vue.use(Vuex);
// 定义一个状态管理容器
let store = new Vuex.Store({
  modules: {
    countModule
  }
});
export default store

也就是现在我们把 store/index.js 文件当作一个所有状态的入口,统一在这里引入所有的状态模块。

不过把状态分割成模块后,我们在组件中调用状态的时候就需要带上模块名了,比如:

// 不能这样获取状态了
this.$store.state.count
// 而是要这样获取
this.$store.state.countModule.count

而调用模块内的方法还是按以前的旧套路不用变,即:

this.$store.commit("add");

对于状态获取状态值的简洁写法也得作相应的修改来获取状态值,其它的三种简洁写法就不用变。

Demo.vue
......
  computed: {
    other() {
      return 1;
    },
    ...mapState({
      count: state => state.countModule.count
    }),
    ...mapGetters({ fillterCount: "fillterCount" })
  },
......

基本的结构是这样子的:

├── store
│  ├── index.js
│  └── countModule
│     └── index.js

Vue vuex 状态管理就分享到这里。

  • 微信扫一扫,赏我

  • 支付宝扫一扫,赏我

声明

原创文章,不经本站同意,不得以任何形式转载,如有不便,请多多包涵!

本文永久链接:http://yunkus.com/vue-vuex/

Leave a Reply

Your email address will not be published. Required fields are marked *

评论 END