likes
comments
collection
share

对Vuex进行一次彻底理解!

作者站长头像
站长
· 阅读数 50

Vuex3版本是2017年出来了,Vuex4版本是2020年出来了,而随着Vue版本的不断迭代,vue2需要安装vuex3版本,vue3需要安装vuex4版本,好的这些都不关我们的事(瞎说,开场白都需要的,你懂我意思吧!😀)那么Vuex究竟是用来干什么的?哒哒哒...经过一番折腾,终于打开Vuex官方文档,官方文档中有一言📚:

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

😵懵了好一阵子...这是个啥,别急别急!我们先不管这句话是什么意思,我们先来回顾一下全局事件总线好吧!我们都知道全局事件总线一般是用来实现多个组件的通信的,是的,下面我们看一下下面例子实现test1组件传递数据给test2组件

// 在main.js中的Vue实例中安装全局事件
new Vue({
  router,
  store,
  render: (h) => h(App),
  beforeCreate(){
    Vue.prototype.$bus = this;// 安装全局事件总线,$bus就是当前应用的vm
  }
}).$mount('#app');

在test1组件中实现传数据给test2组件,通过全局事件emit发送数据:

// test1组件中的一个点击发送事件
 sendData() {
    this.$bus.$emit('hello', 666);
 },

在test2组件中实现获取数据,通过全局事件监听on接收数据:

this.$bus.$on('hello', (data) => {
  console.log('收到数据', data);
});

看了上面的例子,不是很简单就实现了吗,这里只是实现了从test1组件到test2组件传递数据,我们有没有想过假如test2组件也需要传数据给test1组件我们是不是反过来也要写多一次?我们再想想,如果每个组件需要传多个数据时候,是不是重复写多个emit和on事件,这就会导致多个组件间通信复杂,不好维护,这时候Vuex就华丽登场了,Vuex就是为了优雅地解决多个组件通信地问题的。下面我们一次来学习一下Vuex好吧!

1 Vuex是什么?

在学习之前,我们先看看官方文档上的一张Vuex原理图: 对Vuex进行一次彻底理解! 从图上我们可以看到DevTools(调试工具)、Vue Components(Vue组件)、Backend Api(调用后端api)、Vuex这四个主要元素。而Vuex的核心是三大属性:ActionsMutationsState。官方文档不是有五个属性🐎?是的,还有Modules、Getters。为什么图上没有后面两个元素,因为Modules、Getters是用来辅助Vuex的,真正的核心就是前面三个属性。那这几个属性有什么区别?图上的几个属性结合图上的活动箭头又有什么猫腻?我们先来看一下Vuex骨架是怎么样:

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

Vue.use(Vuex);// 先注册

const state = {};
const mutations = {};
const actions = {};
const modules = {};
const getters = {};

export default new Vuex.Store({ // 在实例化
  state,
  getters,
  mutations,
  actions,
  modules,
});
// 在入口文件main.js引入

下面我们将通过一个共享单车的例子一一讲解三个核心属性的操作是如何的?共享单车以两个组件为两个用户,点击相关开锁按钮文字变为锁车,剩余单车减一,点击锁车,剩余单车加一。怎么识别不同用户呢?这里以组件名为识别,当然真实环境是不行的,这里只是用于理解学习。

对Vuex进行一次彻底理解!

  • state:中文意思:状态,它用来存放需要多个组件共用的变量的,从下面的代码可以看出state就是一个对象来的,我们在看看state里面是个啥?其实它就是类似Vue实例的data属性,只不过这里是用在Vuex中的,与data一样为存储变量封装为响应式,有get和set方法。

    对Vuex进行一次彻底理解!

    const state = {
      shareBikeCount: 100,// 当前区域剩余共享单车数量
      bikeUsers: [],// 使用了单车的用户
    };
    
    // 在组件的template中使用state
    this.$store.state.shareBikeCount
    
  • mutations:中文意思:改变,它用来操作state中变量的修改的,它也是一个对象用来定义修改变量操作的函数,而每个函数都有两个参数:state(状态)和value(要修改的值)。

    const mutations = {
      USE_BIKE(state, value) {// 使用车,添加使用用户名
        state.bikeUsers.push(value);
      },
      RETURN_BIKE(state, value) { // 退回车,删除用户名
        const curUserIndex = state.bikeUsers.findIndex((item) => item === value);
        if (curUserIndex > -1) {
          state.bikeUsers.splice(curUserIndex, 1);
        }
      },
      BIKE_COUNT(state, value) { // 单车数量的加减
        if(value==='desc'){
          state.shareBikeCount--;
        }else{
          state.shareBikeCount++;
        }
      },
    };
    
  • actions:中文意思:行动,它也是用来操作state中变量的修改的,是一个对象用来封装定义执行mutations里的函数,函数有两个参数:context和value(要修改的值)。那么context是个啥?我们打印看看如下图,context有五个属性:state(若在模块中则为局部状态)rootState(等同state,只存在模块中), commit,dispatch,getters(等同getters),rootGetters(等同getter是,只存在模块中)。

    对Vuex进行一次彻底理解!

    const actions = {
      useBike(context, value) {
        context.commit('USE_BIKE', value);
        context.commit('BIKE_COUNT', 'desc');
      },
      returnBike(context, value) {
        context.commit('RETURN_BIKE', value);
        context.commit('BIKE_COUNT', 'add');
      },
    };
    

    那么问题来了,既然mutations与actions功能都差不多,为什么会出现两个呢?官方的意思是同步代码与异步代码的区别,mutation中支持同步代码,actions支持异步代码。三个核心属性大概意思我们都知道了,那么dispatch与commit两个动作的作用又是怎么样的?

  • dispatch:中文意思:派遣,一般在组件中调用,然后用来执行actions中的函数,它又是个什么,参数type就是需要执行的actions中的函数名,参数payload是新值,而dispatch是vuex源码中封装的一个函数。

    console.log('dispatch', this.$store.dispatch);
    // 控制台打印
    dispatch ƒ boundDispatch(type, payload) {
    return dispatch.call(store, type, payload);
    }
    
  • commit:中文意思:提交,一般在auctions中调用,然后用来执行mutations中的修改函数,参数type就是需要执行的mutations中的函数名,参数payload是新值,而commit是vuex源码中封装的一个函数。

     console.log('commit', this.$store.commit);
     // 控制台打印
      commit ƒ boundCommit(type, payload, options) {
        return commit.call(store, type, payload, options);
      }
    

    细心地我们有没有发现,根据Vuex原理图在组件中调用dispatch,其实它执行的就是actions里的函数,然后再去执行commit实现mutations里的状态修改。那么问题来了?能不能直接commit实现mutations,其实是可以的,因为mutation中支持同步代码,actions支持异步代码,如果不是异步代码,我们可以直接在组件中调用commit方法来实现mutations,但是一般是推荐使用actions来处理commit方法,方便代码统一,既然我们都实现Vuex中的相关状态管理,下面我们看看组件中是如何处理状态的,下面以test1组件为例:

    对Vuex进行一次彻底理解!

    上面Vuex核心三个属性我们都知道了接下来了解getters、modules属性:

  • getters:用于将state中的数据进行加工,其实它就是类似Vue实例中的computed,如将上面的共享单车文本内容抽取出来使用如下。

    const getters = {
       shareBikeText(state){
          return '当前区域剩余共享单车为'+state.shareBikeCount+'台';
       }
    }
    
  • modules:用于处理多个不同组件间通信模块化。如上面共享单车的处理,把共享单车的数量与用户数量分开模块处理。使用模块下的Vuex状态管理代码如下:

    import Vue from 'vue';
    import Vuex from 'vuex';
    Vue.use(Vuex);
    // 模块可以单独文件然后在index.js中引入,这里在同一文件是为了学习方便
    const bikeModules = {
      namespaced: true,// 是否使用自命名module
      state: {
        shareBikeCount: 100,
      },
      mutations: {
        BIKE_COUNT(state, value) { // 数量的增减
          state.shareBikeCount += value;
        },
      },
      actions: {
        bikeCount(context, value) {
          context.commit('BIKE_COUNT', value); // 共享单车开锁
        },
      },
    };
    const userModules = {
      namespaced: true,
      state: {
        bikeUsers: [],
      },
      mutations: {
        USE_BIKE(state, value) { // 添加使用用户
          state.bikeUsers.push(value);
        },
        RETURN_BIKE(state, value) {// 退回单车用户
          const curUserIndex = state.bikeUsers.findIndex((item) => item === value);
          if (curUserIndex > -1) {
            state.bikeUsers.splice(curUserIndex, 1);
          }
        },
      },
      actions: {
        useBike(context, value) {
          context.commit('USE_BIKE', value);
        },
        returnBike(context, value) {
          context.commit('RETURN_BIKE', value);
        },
      },
    };
    export default new Vuex.Store({
      modules: {
        a: bikeModules,
        b: userModules,
      },
    });
    

    a,b就是使用了允许命名空间的,如果没有设置namespaced为true,a,b是不生效,只能直接引入对应module名。 下面是test1组件中调用Vuex的代码如下:

    对Vuex进行一次彻底理解! 模块化分为全局模块:存放全局使用共享的数据和局部模块:模块内单独管理,全局模块不需要开启命名空间。 好了好了,到了现在我们应该知道Vuex是什么了吧?Vuex就是用来存储数据的地方,最核心的技能就是共享,什么情况下需要用到Vuex?

  • 多个组件依赖同一状态- 共享。

  • 来自不同组件的行为需要变更同一状态 - 共享。

2 Vue优雅使用Vuex

如果业务比较简单,没有涉及到多个组件多个状态的管理,单纯实现上面的Vuex就已经够用,但是如果使用场景更为复杂,我们就需要优化代码了,增强代码的维护,这就涉及到了mapStatemapGettersmapMutationsmapActions。而这些属性的获取又分为两种情况:模块下和非模块下:

2.1 非模块下的map~系列

  • mapState:对state中所有状态进行映射,也就是读取state中所有状态,在组件中的计算属性直接使用,我们就可以直接在template中直接使用状态了。
    // 方式一:对象模式:可以自定义映射的状态名称,也可以实现对state的处理
    computed:...mapState({count:'shareBikeCount',count: (state) => state.shareBikeCount});
    // 方式二:数组模式:映射数组内元素值与state中状态名称一致。
    computed:...mapState(['shareBikeCount']);
    
  • mapGetters:对getters中所有属性进行映射,也就是读取getters中所有属性,使用方式与mapState使用方式一致。
  • mapMutations:对mutations所有属性函数进行隐射,也就是读取mutations中所有属性方法。注意地,如果需要传参数,直接在template中调用时直接传
    // 方式一:对象模式:可以自定义映射的状态名称
    ...mapMutations({'useBike':'useBike',returnBike: 'returnBike'});
    // 方式二:数组模式:映射数组内元素值与mutations中属性名称一致。
    ...mapMutations(['useBike','returnBike']);
    
  • mapActions:对actions所有属性函数进行隐射,也就是读取actions中所有属性方法。方式与mapMutations使用方式一致。

2.2 模块下的map~系列

如果Vuex中是模块化管理的话,如果使用map系列需要传两个参数:模块名需要调用的对应属性里的属性,注意地,如果开启了命名空间可以使用自定义地模块名比如下面例子的a、b,否则使用模块名如:bikeModules、userModules。各属性在组件中的使用情况如下:

// state方式一
this.$store.state.a.shareBikeCount;// 直接读取
// state方式二
...mapState('a',['shareBikeCount']);// 这样就可以直接在template中使用shareBikeCount
// getter方式一
this.$store.getter['a/需要获取的属性名'];// 直接读取
// getter方式二
...mapGetters('a',['需要获取的属性名']);// 这样就可以直接在template中使用属性
// dispatch方式一
this.$store.dispatch('a/bikeCount',1);// 代表数量加1操作
// dispatch方式二
...mapActions('a',{count:'bikeCount'});
// commit方式一
this.$store.commit('b/useBike',value);// 用户添加
// commit方式二
...mapMutations('b',{use:'useBike'});

3 Vue3优雅使用Vuex

当前版本的Vuex实例化不通过new Vuex.store({})实例化Vuex了,是通过createStore({}),Vuex也不需要全局注册store了,在setup中使用useStore()创建引入。在这里就不一一讲解了,用法与vue2差不多,只是Vue3内部语法引入不一样了。

4 Vuex进阶插件pinia

4.1 pinia是什么?

  • pinia:Pinia是Vue专属状态管理库,它允许你跨组件或页面共享状态。可以看出,pinia的功能与Vuex功能是一样的,那么问题来了,既然有了Vuex,为什么还要用pinia,这就需要考虑它的优点:

    • 支持Vue2、Vue3。
    • 只有state、getter、action属性。
    • pinia的action支持同步和异步。
    • 可以很方便访问其他store的action。
    • 良好的ts支持。
    • 无需创建模块管理了。
    • 体积非常小,只有1kB左右。

4.2 如何使用pinia插件?

  • 在入口文件引入:

    import App from './App.vue'
    import { createApp } from 'vue'
    import { createPinia } from 'pinia' //导入pinia
    const  pinia = createPinia(); //调用创建pinia
    const app = createApp(App);
    app.use(pinia);
    
  • 用pinia来再次使用上面的共享单车例子,pinia中没有模块化,它每创建一个defineStore就是单独一个store,defineStore需要传两个参数,第一个参数是相当于每个store的id,第二个参数就是状态管理的核心了,代码如下:

    import { defineStore } from 'pinia' //导出 pinia仓库
    export const useBikeStore = defineStore('bike', { 
        state: () => { //相当于全局的 data()
            return {
                shareBikeCount: 100,
                bikeUsers: [],
            }
        },
        getters: {
        },  //相当于全局的computed
        actions: {
            useBike(value) {
                this.bikeUsers.push(value);
                this.shareBikeCount--;// 可以调用当前store的getters属性
            },
            returnBike(value) {
                const curUserIndex = this.bikeUsers.findIndex((item) => item === value);
                if (curUserIndex > -1) {
                    this.bikeUsers.splice(curUserIndex, 1);
                    this.shareBikeCount++;
                }
            },
        }   //相当于全局methods
    })
    

    从上面我们可以看出,相对于Vuex,pinia插件的使用更方便而且更简洁了,没有mutations属性,那么我们再看看在组件中是如何引入pinia,下面也是以test1组件为例子:

    <template>
      <div>
        <h2 v-if="!isUsed">test1需要借用共享单车</h2>
        <h2 v-if="isUsed">test1正在使用共享单车</h2>
        <div>
          <button v-on:click="updateUsedBike()" v-if="!isUsed">开锁</button>
          <button v-on:click="updateReturnBike()" v-if="isUsed">锁车</button>
          <span>当前剩余共享单车:{{ shareBikeCount }} 台</span>
        </div>
        <div>当前用户有:{{ bikeUsers }}</div>
      </div>
    </template>
    <script setup>
    import { ref } from 'vue';
    import { useBikeStore } from '../store/pinia';
    import { storeToRefs } from 'pinia';
    const isUsed = ref(false);
    const store = useBikeStore();
    const {shareBikeCount,bikeUsers} = storeToRefs(store);
    const updateUsedBike = ()=>{
      isUsed.value=true;
      store.useBike('test1');
    }
    const updateReturnBike = ()=>{
      isUsed.value=false;
      store.returnBike('test1');
    }
    </script>
    

    注意地,当使用store的过程中,如果直接对store进行解构,会破坏数据的响应,所以pinia提供了storeToRefs用来进行解构

Vuex总结

  • Vuex核心属性:state、mutations、actions,状态的管理主要是靠这三个属性,Vuex的核心操作是dispatch和commit,而Vuex的核心意义在于状态的共享,通过state定义多个组件共享的变量,然后在mutations中修改变量的值,同步异步都可以通过actions执行,dispatch可以通俗理解为执行actions中函数,commit执行的是mutations中函数实现state的状态改变。同步代码可以直接执行dispatch。
  • Vuex的mapState、mapMutations、mapActions、mapGetters系列都是用来优化Vuex代码的,方便我们进一步管理。
  • Pinia插件推荐使用,代码量减少了,更方便我们去使用Vuex,简化了Vuex结构,使用起来更加舒服爽滑。

参考资料

Vuex官方文档

pinia官方文档

一文搞懂pinia状态管理

转载自:https://juejin.cn/post/7179961655235706941
评论
请登录