5分钟带你熟悉Vuex操作 | 通俗易懂的Vuex入门

487 阅读2分钟

这是我参与8月更文挑战的第4天,活动详情查看: 8月更文挑战” 。

Vuex是什么?

Vuex是一个专为Vue.js应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。 这个状态我们可以理解为在data中的属性,需要共享给其他组件使用的部分。也就是说,是我们需要共享的data,使用vuex进行统一集中式的管理。

1. Vuex的构成

4-1.png

图中绿色虚线框起来的部分就是Vuex的核心。后续我们会详细介绍图中的核心部分。

每一个Vuex应用的核心就是stor(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state) 。每个vue应用有且仅有一个store对象。

const store = new Vuex.Store({
  state: {},
  getters: {},
  mutations: {},
  actions: {},
  modules: {}
});

可见store是Vuex.Store这个构造函数new出来的实例。在构造函数中可以传一个对象参数。这个参数中可以包含5个对象:

  • state – 存放状态
  • getters – state的计算属性
  • mutations – 更改state状态的逻辑,同步操作
  • actions – 提交mutation,异步操作
  • mudules – 将store进行模块化,每一个module有自己的state, getters, mutations, actions等

Vuex和单纯的全局对象有以下两点不同:

  1. Vuex的状态存储是响应式的。当Vue组件从store中读取状态的时候,若store中的状态发生变化,那么相应的组件也会相应地得到高效更新。
  2. 不能直接改变store中的状态。改变store中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

什么情况下应该使用Vuex?

  1. 小应用不建议使用Vuex,因为小项目使用Vuex可能会比较繁琐冗余。
  2. 中大型单页应用,因为要考虑如何更好地在组件外部管理状态,Vuex将会成为自然而然的选择。

Vuex的使用与核心概念

在介绍Vuex的核心概念之前, 使用vue-cli初始化了一个项目,就会生成如下格式的代码,接下来以代码的形式来说明Vuex的核心概念。

  • main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
Vue.config.productionTip = false;
Vue.prototype.$bus = new Vue({
  router,
  store,
  render: h => h(App)
}).$mount("#app");
  • store文件夹index.js
import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  modules: {}
});

1. State

state是Vuex中的公共的状态。

便于理解: 可以将state看作是所有组件的data, 用于保存所有组件的公共数据。

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {},
  actions: {},
  modules: {}
});


在组件中使用

<template>
  <!-- vuex基本使用 -->
  <div>{{ count }}</div>
</template>

<script>
export default {
  data() {
    return {};
  },
  computed: {
    count() {
      return this.$store.state.count;
    }
  }
};
</script>

2. Getters

有时候我们需要从store中的state中派生出一些状态,此时可以用到getters,getters可以看作是store的计算属性。有两种访问方式,方式一通过属性访问,接受state作为其第一个参数,也可以接受其他getter作为第二个参数。不支持用户随意传入参数。方式二通过方法访问,通过让getter返回一个函数,来实现getter传参。

便于理解: 可以将getters属性理解为所有组件的computed属性,也就是计算属性。通过属性访问时getters的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。通过方法访问时,每次都会去进行调用,而不会缓存结果。

state: {
    count: 0,
    list: [
      { id: 1, title: "产品名称一", status: 1 },
      { id: 2, title: "产品名称二", status: 0 },
      { id: 3, title: "产品名称三", status: 1 }
    ]
  },
  getters: {
    // 访问方式一:通过属性访问
    // getter接受state作为其第一个参数,也可以接受其他getter作为第二个参数。不支持用户随意传入参数
    // 一个参数:
    filterList: state => {
      return state.list.filter(item => item.status == 1);
    },
    // 两个参数
    listLen: (state, getters) => {
      return getters.filterList.length;
    },
    // 访问方式二:通过方法访问
    // 通过让getter返回一个函数,来实现getter传参
    getListById: state => id => {
      return state.list.filter(item => item.id == id);
    }
  },

在组件使用

computed: {
    count() {
      return this.$store.state.count;
    },
    list() {
      return this.$store.getters.filterList;
    },
    listLen() {
      return this.$store.getters.listLen;
    },
    getListById() {
      return this.$store.getters.getListById(1);
    }
  }

3. Mutations

mutations里面是同步更改state中状态的逻辑。更改Vuex中的state的唯一方法是提交mutation。每个mutation都有一个字符串的 事件类型 (type)  和 一个 回调函数 (handler) 。这个回调函数就是我们实际进行状态更改的地方,回调函数的第一个参数是state, 第二参数是mutation的载荷(payload), 也就是自定义的参数。可以使用常量替代mutation事件类型

便于理解: 可以将mutaions理解为store中的methodsmutation必须是同步函数,如果非要写成异步后果就是在view视觉看到count值改变了但是在store的state属性中count的值并没有改变。

mutations: {
    increament(state) {
      state.count++;
    },
    // 提交载荷(Payload)
    increamentPayload(state, payload) {
      state.count += payload.n;
    },
    // 使用ES2015风格的计算属性命名功能来使用一个常量作为函数名
    [DOUBLE_INCREAMENT](state) {
      state.count += 2;
    },
    // 错误示范:mutation不支持异步提交,这是错误的写法,在view视觉看到count值改变了但是在store的state属性中count的值并没有改变
    increamentAsyncError(state) {
      setTimeout(() => {
        state.count++;
      }, 1000);
    }
  },

type.js

export const DOUBLE_INCREAMENT = "doubleIncreament";

在组件中使用

import { DOUBLE_INCREAMENT } from "./type";
...

methods: {
    increament() {
      this.$store.commit("increament");
    },
    // 提交载荷
    increamentPayload() {
      this.$store.commit("increamentPayload", { n: 10 });
    },
    // 对象风格的提交方式
    increamentOhter() {
      this.$store.commit({
        type: "increamentPayload",
        n: 10
      });
    },
    // 使用ES2015风格的计算属性命名功能来使用一个常量作为函数名
    doubleIncreament() {
      this.$store.commit("doubleIncreament");
    },
    // 错误示范
    increamentAsyncError() {
      this.$store.commit("increamentAsyncError");
    },
  }

4. Actions

因为mutations中只能是同步操作,但是在实际的项目中,会有异步操作,那么actions就是为了异步操作而设置的。这样,就变成了在action中去提交mutation,然后在组件的methods中去提交action。

actions: {
    increamentAsync(context) {
      setTimeout(() => {
        context.commit("increament");
      }, 1000);
    },
    increamentAsyncPayload(context, payload) {
      setTimeout(() => {
        context.commit("increamentPayload", payload);
      }, 1000);
    },
    // 使用参数解构,简化代码
    increamentAsync2({ commit }) {
      setTimeout(() => {
        commit("increament");
      }, 1000);
    }
  },

在组件中使用

methods: {
    // 分发action
    increamentAsync() {
      this.$store.dispatch("increamentAsync");
    },
    // 以载荷形式分发
    increamentAsyncPayload() {
      this.$store.dispatch("increamentAsyncPayload", { n: 20 });
    },
    // 以对象方式分发
    increamentAsyncOther() {
      this.$store.dispatch({
        type: "increamentAsyncPayload",
        n: 10
      });
    },
    // 使用参数解构,简化代码
    increamentAsync2() {
      this.$store.dispatch("increamentAsync2");
    }
  }

二者的区别:

  • actions提交的是mutations而不是直接变更状态
  • actions中可以包含异步操作, mutations中绝对不允许出现异步
  • actions中的回调函数的第一个参数是context, 是一个与store实例具有相同属性和方法的对象。可以使用ES2015 的 参数解构
  • actions提交的时候使用的是dispatch分发函数,mutations则是用commit提交函数。

5. Modules

module是为了将store拆分为一个个小模块,这么做的目的是因为当应用变得非常复杂时,store对象就有可能变的很大、很臃肿,分成模块的话方便管理。每个module拥有自己的state, getters, mutation, action。对于模块内部的mutation和getter,接受的第一个参数是模块的局部状态state。

小结

Vuex是通过全局注入store对象,来实现组件间的状态共享。在大型复杂的项目中(多级组件嵌套),需要实现一个组件更改某个数据,多个组件自动获取更改后的数据进行业务逻辑处理,这时候使用vuex比较合适。假如只是多个组件间传递数据,使用vuex未免有点大材小用,其实只用使用组件间常用的通信方法即可。

附:演示完整的源码

  • store index.js
import Vue from "vue";
import Vuex from "vuex";
import { DOUBLE_INCREAMENT } from "./type";

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 0,
    list: [
      { id: 1, title: "产品名称一", status: 1 },
      { id: 2, title: "产品名称二", status: 0 },
      { id: 3, title: "产品名称三", status: 1 }
    ]
  },
  getters: {
    // 访问方式一:通过属性访问
    // getter接受state作为其第一个参数,也可以接受其他getter作为第二个参数。不支持用户随意传入参数
    // 一个参数:
    filterList: state => {
      return state.list.filter(item => item.status == 1);
    },
    // 两个参数
    listLen: (state, getters) => {
      return getters.filterList.length;
    },
    // 访问方式二:通过方法访问
    // 通过让getter返回一个函数,来实现getter传参
    getListById: state => id => {
      return state.list.filter(item => item.id == id);
    }
  },
  mutations: {
    increament(state) {
      state.count++;
    },
    // 提交载荷(Payload)
    increamentPayload(state, payload) {
      state.count += payload.n;
    },
    // 使用ES2015风格的计算属性命名功能来使用一个常量作为函数名
    [DOUBLE_INCREAMENT](state) {
      state.count += 2;
    },
    // 错误示范:mutation不支持异步提交,这是错误的写法,在view视觉看到count值改变了但是在store的state属性中count的值并没有改变
    increamentAsyncError(state) {
      setTimeout(() => {
        state.count;
      }, 1000);
    }
  },
  actions: {
    increamentAsync(context) {
      setTimeout(() => {
        context.commit("increament");
      }, 1000);
    },
    increamentAsyncPayload(context, payload) {
      setTimeout(() => {
        context.commit("increamentPayload", payload);
      }, 1000);
    },
    // 使用参数解构,简化代码
    increamentAsync2({ commit }) {
      setTimeout(() => {
        commit("increament");
      }, 1000);
    }
  },
  modules: {}
});

  • store type.js
export const DOUBLE_INCREAMENT = "doubleIncreament";
  • test.vue
<script>
export default {
  data() {
    return {};
  },
  computed: {
    count() {
      return this.$store.state.count;
    },
    list() {
      return this.$store.getters.filterList;
    },
    listLen() {
      return this.$store.getters.listLen;
    },
    getListById() {
      return this.$store.getters.getListById(1);
    }
  },
  methods: {
    increament() {
      this.$store.commit("increament");
    },
    // 提交载荷
    increamentPayload() {
      this.$store.commit("increamentPayload", { n: 10 });
    },
    // 对象风格的提交方式
    increamentOhter() {
      this.$store.commit({
        type: "increamentPayload",
        n: 10
      });
    },
    // 使用ES2015风格的计算属性命名功能来使用一个常量作为函数名
    doubleIncreament() {
      this.$store.commit("doubleIncreament");
    },
    // 错误示范
    increamentAsyncError() {
      this.$store.commit("increamentAsyncError");
    },
    // 分发action
    increamentAsync() {
      this.$store.dispatch("increamentAsync");
    },
    // 以载荷形式分发
    increamentAsyncPayload() {
      this.$store.dispatch("increamentAsyncPayload", { n: 20 });
    },
    // 以对象方式分发
    increamentAsyncOther() {
      this.$store.dispatch({
        type: "increamentAsyncPayload",
        n: 10
      });
    },
    // 使用参数解构,简化代码
    increamentAsync2() {
      this.$store.dispatch("increamentAsync2");
    }
  }
};
</script>