【Vue3实践】(七)Vue中的全局状态管理 Vuex与Pinia的使用

时间:2022-12-28 01:15:43

1.前言

由于在日常开发中会有一部分前端的开发任务,会涉及到Vue的项目的搭建、迭代、构建发布等操作,所以想系统的学习一下Vue相关的知识点,本专题会依照Vue的搭建、开发基础实践、进阶用法、打包部署的顺序进行记录,同时也会提到一些功能在实际生产环境中的使用。

历史文章链接如下:

《Vue3搭建、路由配置与基本语法》
《响应式变量、双向绑定、计算属性、监听器》
《优雅使用VUE3 组件特性:组件定义、组件注册、事件监听、双向绑定》
《优雅使用VUE3 组件特性:属性透传、依赖注入、组件插槽、动态组件》
《生命周期函数、组合式函数的使用》
《Vue3使用vite处理环境变量、打包部署、nginx配置》

最近在公司开发的时候得到这么一个需求,需要给一个旧的系统加上一些按钮权限,考虑了插件、透传、依赖注入等方式来实现,但是都有一定的局限性,最终采用了Vuex全局状态管理的方式来实现了这个需求。

本篇会讲解一下Vue中状态的概念,以及全局状态组件 VuexPinia 的使用,这里之所以会使用Vuex,是因为这个旧的项目是通过Vue2进行编写的,如果是Vue3项目的话,推荐使用Pinia,比Vuex更轻量、更简洁、功能更加强大。

2.状态的概念

在学习这两个组件之前,我们先了解一下什么是状态

其实,只要使用过Vue的响应式变量,就已经使用过状态了,我们将Vue的组件可以分成3个部分,分别是:状态、视图、交互

  • 状态:所谓的状态,就是在Vue组件中的数据部分,用于驱动应用的数据源。
  • 视图:对状态的映射,也就是通过数据渲染出的DOM元素。
  • 交互:监控了视图中的数据输入动作,将视图中输入的值传递给数据源。

他们3者可以形成一个单项的数据流,即:状态的变更会触发视图的变化,用户在视图上输入也会通过交互传递给状态。


上面所说的状态,基本上局限与某个特定的组件中,而我们在实际的开发中可能会有这样的一种需求:提供一个数据源,让所有组件都可以使用,我们可以简单的把这样的一种数据源叫做全局状态,可以使用Vuex或Pinia来实现。

之所以不用组件的逐级透传和依赖注入的方式,是因为有时候组件并不完全在同一个组件树上。我们针对不同的业务功能模块,可能会有不同的组件树根节点,如下图所示:
【Vue3实践】(七)Vue中的全局状态管理 Vuex与Pinia的使用
在这样的模块划分下,不管是透传还是依赖注入,都需要在各自的根节点上定义一次“全局状态”,供子节点使用,这样依然会有重复的代码。

3.Vuex管理状态

接下来就通过实现上述需求的方式,来讲解Vuex的简单使用,更详细的使用方式可以参考《Vuex文档》

先根据需求拆解一下需要完成的任务:

  1. 需要通过一个全局状态,让所有组件可以直接使用。
  2. 需要提供一个setter方法,修改这个全局状态的值。
  3. 由于权限列表是来自于后端服务,所以需要有一个异步请求操作。

综上,会使用到Vuex中的 StateMutationAction 三种特性,其中 State 就是提供给各个组件使用的全局特性,Mutation 则是 Stage 的修改入口,Action 是异步操作,用于异步提交 Mutation。

此外,可以利用插件系统将定义好的功能注入到各个组件中,让组件可以直接使用。

3.1.Vuex安装及功能定义

因为我这里是一个Vue2的旧版本代码库,不支持Vuex4以上的版本,所以选择了一个3.x的版本,通过npm安装。

npm install vuex --save @3.6.2

安装完成之后,在src根目录下创建一个store目录,并创建index.js文件,内容如下:

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

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
    // 权限全局状态
    authMap: {}
  },
  mutations: {
    // 定义更新权限的setter方法
    setAuthMap(state, authMap) {
      state.authMap = authMap;
    }
  },
  actions: {
    // 定义一个异步请求方法,用于获取权限
    batchAuth(context) {
      // 此处是模拟数据,实际开发中替换了axios发起请求
      let data = {
        "user:list": true,
        "user:add": false
      }
      context.commit('setAuthMap', data);
    }
  }
})

// 导出组件
export default store

3.2.全局注入及使用

想要任意组件都可以直接使用上面定义好的store,则需要在main.js中通过插件机制进行注入:

import Vue from "vue";
import App from "./App.vue";
import store from "./store"; // 引入 store 文件

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

注册完成之后,就可以通过this.$stroe使用了。

由于权限列表并不会经常变化,所以我们只需在进入系统时查询一次即可,即:在App.vue的生命周期钩子中执行,通过 dispatch 调用action中定义的异步方法。

mounted() {
  this.$store.dispatch('batchAuth');
}

然后在对应组件的按钮上,也可以直接使用store,例如:

<el-button type="primary" @click="openDialog()" 
	v-if="$store.state.authMap['user:add']">新增用户
</el-button>

此时由于user:add为false,这里的新增用户按钮就会对当前的使用者隐藏。其他的使用方式也是类似的,我们可以从authMap通过某个固定的key取出一个布尔值,则可以*的处理DOM的状态,例如:v-show:disabled等等。

4.Pinia管理状态

如果是一个新的Vue3项目,使用Pinia来管理状态是一个更佳的选择,配合组合式API使用非常简洁易用。

4.1.Pinia中的核心概念

Pinia中有4个核心的概念,分别是:store , state , getter , action

  • store :用于来承载全局状态的实体,它不会挂接在某个组件树上,而是每个组件都可以直接引入并使用它。
  • state:是应用程序的状态数据,我们可以上面定义自己需要的任何数据,例如用户信息、页面状态、列表数据等。state在组件式API中类似于data(),在组合式API中类似于ref
  • getter:用于获取state中数据的函数,并做二次计算,可用于计算衍生数据、过滤数据等场景。gettercomputed操作类似。
  • action:是store中的函数,多用于修改state的值,它类似于method的作用。

4.2.Pinia的定义与使用

安装并配置pinia:

npm install pinia
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'

const pinia = createPinia()
const app = createApp(App)

// 全局组件引入
app.use(pinia)

// 挂载应用
app.mount('#app')

接下来是创建store,类似于Vuex的做法,我们可以./store目录下创建一个useAuthMapStore.js文件,通过defineStore创建一个store对象,对象名遵循以use开头,以store结尾的规则,第一个参数是当前对象的id,需要保证唯一,第二个参数就是选项式API中的选项 option。

综上,上面的Vuex 定义的authMap,用Pinia来编写就简洁多了。

import { defineStore } from "pinia";

export const useAuthMapStore = defineStore("authMap", {
  state: () => ({ authMap: {} }),
  actions: {
    // async 异步调用
    async batchAuth() {
      this.authMap = {
        "user:list": true,
        "user:add": false
      };
    }
  }
});

在APP.vue中触发batchAuth方法:

<script setup>
import { useAuthMapStore } from "@/store/module/useAuthMapStore";
useAuthMapStore().batchAuth();
</script>

在任意组件中使用:

<template>
  <div class="hello">
    <div> {{ store.authMap }}</div>
  </div>
</template>

<script setup>
import { useAuthMapStore } from "../store/module/useAuthMapStore";
const store = useAuthMapStore();
</script>

【Vue3实践】(七)Vue中的全局状态管理 Vuex与Pinia的使用

4.3.使用组合式API定义Store

如果是使用组合式API,也可以这么写:

import { defineStore } from "pinia";
import { ref } from "vue";

export const useAuthMapStore = defineStore("authMap", () => {
  const authMap = ref({});

  function batchAuth() {
    authMap.value = {
      "user:list": true,
      "user:add": false
    };
  }

  return { authMap, batchAuth };
});

使用的方式和选项式API的使用方式是一样的,个人更建议使用组合式API来定义Store,在Vue3上的支持性更好,更灵活,风格也统一。


在上述的例子中,还没有提到如何使用getter方法,在组合式API中的getter方法其实就是一个属性计算,通过computed实现即可,改造后的代码如下:

export const useAuthMapStore = defineStore("authMap", () => {
  const authMap = ref({});

  const anotherAuthMap = computed(() => {
    let value = JSON.parse(JSON.stringify(authMap.value));
    value["user:view"] = true;
    return ref(value);
  });

  function batchAuth() {
    authMap.value = {
      "user:list": true,
      "user:add": false
    };
  }

  return { authMap, batchAuth, anotherAuthMap };
});

然后验证一下结果:

<template>
  <div class="hello">
    <div> {{ store.authMap }}</div>
    <div> {{ store.anotherAuthMap }}</div>
  </div>
</template>

【Vue3实践】(七)Vue中的全局状态管理 Vuex与Pinia的使用

5.总结

本篇主要讲述Vue中的状态、全局状态的概念以及如何通过Vuex和Pinia来管理全局状态,其中Vuex更适合与Vue2的旧项目,而Pinia适合与使用Vue3构建的新项目,Pinia使用起来更加轻量和简洁,配合组合式API使用效果更加。

关注以下的知识点:

  • Vuex:
    • 核心概念:StateMutationAction,State用于定义数据,Mutation用于修改数据,Action是异步执行的方法,往往是用于异步获取数据后调用Mutation修改数据。
    • Vuex可以通过插件进行全局注入,通过this.$store语法进行调用,其中this.$store.state用于获取状态数据,this.$store.dispatch 用于异步调用Action中的函数。
  • Pinia
    • 核心概念:分别是:store , state , getter , action
      • store :用于来承载全局状态的实体,它不会挂接在某个组件树上,而是每个组件都可以直接引入并使用它。
      • state:是应用程序的状态数据,我们可以上面定义自己需要的任何数据,例如用户信息、页面状态、列表数据等。
      • getter:用于获取state中数据的函数,并做二次计算,可用于计算衍生数据、过滤数据等场景。
      • action:是store中的函数,多用于修改state的值。
    • 定义:通过defineStore定义一个store对象,第一个参数为对象id,第二个参数则是定时对象体。
    • 命名:通常以use开头,以store结尾,建议文件名与store对象名保持一致,例如:useCounterStoreuseCounterStore.js