【前端】通俗易懂学习Vuex

1,242 阅读9分钟

前言

在学习Vuex之前,进行兄弟组件的传值使用最多的是使用第三个组件(EventBus)或者父组件,这种方式虽然对小项目比较友好,但是对于企业项目或规模比较大的项目就比较麻烦。而在Vue官方文档,提供了一种状态管理的方式Vuex,组成了Vue生态的重要部分。Vuex可以将复杂的大型项目的数据管理得清晰条理性,让数据流在每一个组件和模块变得可控、可追溯,这样使得它成为项目开发中的大杀器。


1 什么是Vuex?

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

个人理解:Vuex就是仓库管理员,负责对每一类别的商品(data)进行统一管理,实现分门别类、快速查找和获取等功能,真正做到像商品一样管理数据。哪个地区需要相应的商品便会配送过去,对应的仓库的商品也会对应进行改变。Vuex这个仓库可以实现对全局状态数据的管理,可以方便快捷的实现组件之间数据的共享。

那么,使用`Vuex`进行数据状态的统一管理有什么好处呢?
  • 能够在Vuex中进行集中管理共享的数据,便于开发和后续维护
  • 能够高效地实现组件之间的数据共享和通信,提高开发效率
  • 存储在Vuex中的数据是响应式的,能够实时保持数据与页面的同步
一直说`Vuex`是进行数据状态管理的一种模式,那么什么样的数据适合存储在`Vuex`中呢?

就实际开发情况而言,一般的小项目可以不使用Vuex,也能做到很好的数据管理。对于较为大型的企业项目可以采用Vuex,对组件之间共享的全局数据进行管理,而对于组件中的私有数据,依旧保存在组件自身的data中进行处理。


2 什么是“状态管理模式”?

在官方文档提到了“状态管理模式”这个词语,那么到底什么是“状态管理模式”呢?如果你想知道,就跟着我的脚步,带你一起研究。

简要版的状态管理模型包括:stateview以及actions等三个部分。

  • state相当于组件中的data,负责用于存放全局的数据(即仓库),驱动应用的数据源。
  • view是数据待渲染展示的视图页面,可以将state数据映射到页面上。
  • actions就如同字面意义进行行动、变化,对在view上做出的数据改变做出响应。

如下图所示,是一个简单的“单向数据流”模型的示意图,看似是一个稳定的三角形结构,但其实它的数据流一点也不稳定。当多个组件同时共享这个“单向数据流”模型的数据时候,我们需要考虑的是多个视图同时依赖于同一个数据状态,以及来自多个不同视图的行为需要变更同一数据状态。

我们知道在之前的组件传参是通过层级嵌套进行数据传递,这就导致和千层饼一样,一层套一层,老套娃了,而对于兄弟组件的传值却无能为力。而面对数据状态的更新和同步时,采取的方法往往是通过父子组件直接引用或者事件驱动的形式。这种方式操作繁杂、难以维护。

Vuex的实现原理就是将组件的共享的状态数据抽取放在仓库中,以一个全局的“状态管理模式”触发数据在视图上进行渲染。将全局的组件构建成的一个巨大的视图树,而数据就相当于树干,能够把数据流传递到每一个视图子叶上,任何组件都能获取状态或者触发行为!

我们可以在下面的Vuex的构成关系图中可以看出:

  • State是作为数据管理的仓库、数据存储的容器。
  • Getter相当于组件中的Computed属性,getter的返回值会根据依赖进行缓存,当依赖值发生变化时会重新计算进行使用。
  • Mutation是用于更新store中的state数据状态的唯一方法,其相当于组件中的methods,但是它不能进行异步方法的操作(定时器、axios异步请求等)。
  • Actions其实和Mutation的作用类似,只不过ActionsVuex中用于专门处理异步方法的,通过操作Mutation进行更新数值状态。
  • Module是用于对模块进行分割管理的,当数据比较庞大时,在同一个文件中不便于管理,此时可以按照功能需求进行模块封装。
  • Plugins是对状态进行快照、记录和追溯等功能的插件,可以实现对数据状态的便捷管理。
  • 辅助函数mapStatemapGettersmapActionsmapMutations等是便于开发者在组件的vm中处理store数据,便于管理操作。当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余。为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性。

个人理解:


3如何使用Vuex?

最简单的Vuex示例
下面代码是一个最简单`Vuex`示例demo,可以看到每一个项目的`Vuex`应用都是一个`store`,而`store`中的数据状态不能直接进行操作,而必须通过`mutations`中方法进行同步更新数据。也就是,在开发中不能更改`state`中`count`的数值,而必须通过`mutations`中的`increment()`方法进行自增操作。

store.js代码:

import Vue from 'vue';
import Vuex form 'vuex';

Vue.use(Vuex);
const store = new Vuex.Store({
    //state相当于数据的仓库,类似于组件中的data
    state: {
        count0,
        todos: [
          { id1text'...'donetrue },
          { id2text'...'donefalse }
        ]
    },
    // 定义"getter"(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
    getters:{
      //筛选出所有todo.done为true的值
      doneTodosstate => {
        return state.todos.filter(todo => todo.done)
      }
    }, 
    //mutations相当于组件中的methods,用于处理同步事件
    mutations: {
        //自增操作
        increment(state) {
            state.count++
        },
        //自减操作--这里使用了两个参数,state是表示仓库的数据,payload表示要操作的载荷对象
        decrease(state,payload){
          state.count -= payload.decNum//payload.decNum表示每次递减的数据
        }
    },
    // 处理异步事件
    actions: {
      increment(context){
        context.commit("add");
      },
      /* 
      increment({commit}){//此处使用了对象解构
        commit("add");
      }
      */
      incrementAsync({commit}){
         setTimeout(()=>{
           commit("add");
         },1000)
       }
    }
})

test.js代码:

<template>
  <div>
    <h1>数据:{{$store.state.count}}</h1>
    <h1>采用计算属性:{{count}}</h1>
    <button @click="addCount()">数据+1</button>
  </div>
</template>

<script>
export default {
  computed:{
    count(){
      //此处的store已经进行了全局挂载
      return this.$store.state.count;
    },
    //通过store.getters对象调用
    doneTodos () {
      return this.$store.getters.doneTodos
    }
  },
  methods:{
    addCount(){
      //调用仓库中的increment方法进行count自增操作
      this.$store.commit("increment");
    },
    // 进行异步操作
    addAsync(){
      this.$store.dispatch("incrementAsync");
    },
    
  }
}
</script>

仓库中的count数据状态除了可以在标签中使用$store.state.count进行获取,也可以使用computed进行获取,当然其实一样的。我们看到这样每次通过computed获取store中对应的数据是不是很复杂,而Vuex提供了相应的数据映射mapState在相应的组件批量导入数据,极大简化了我们的操作。同样的,我们也能使用辅助函数mapGetters进行映射计算,辅助函数mapMutations可以将组件中的methods映射为store.commit,辅助函数mapActions将组件中异步函数映射为store.dispatch,达到简化代码的目的。

import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
export default {
    /*
      computed: {{
          count: state => state.count,
          countAlias: 'count',    // 别名 `count` 等价于 state => state.count
      })}
    */
    //更加简化的版本
    computed:{
      ...mapState(["count"]),//映射this.count为store.state.count
      ...mapGetters(["doneTodosCount"]),//使用对象展开方式将getters混入computed中
      ...mapMutations([
        increament,//注意此处必须是处理同步函数
      ])
    },
    methods:{
      ...mapActions(["incrementAsync"])
    }
    
}

上面代码中storeVue中进行了全局挂载,这样在项目就可以进行全局操作使用Vuex中的数据,可以将数据流注入所有的Vue组件进行使用。 main.js代码:

import Vue from "vue";
import App from "./App.vue";
import store from "./store";

Vue.config.productionTip = false;

new Vue({
  router,
  store,
  render(h) => h(App),
}).$mount("#app");

结语

我愿意用最通俗易懂的语言去表达我对Vuex的理解,如果在理解有偏差还请大家指出,谢谢。

参考文档

  • https://juejin.cn/post/6844903558496665607
  • https://juejin.cn/post/6844903784708046855
  • https://vuex.vuejs.org/zh/guide/actions.html