Vuex应用状态管理


Vuex 应用状态管理

1.Vuex 解决的问题及使⽤场景

⼀、本节⼤纲

  • 组件开发中头疼的的问题
  • 状态是什么?
  • Vuex 状态管理
  • Vuex 使⽤场景

⼆、组件开发中头疼的的问题

在之前学习组件的时候,我们了解到了⼀些组件之间传递数据的⽅法,可以⽐较⽅便地实现⽗⼦组件之间的数据通信。或者通过路由实现参数传递也是组件之间数据通信的⼀种⽅式。

但是我们仍然会遇到⼀些问题:

  • 数据⼀般都是在⽗组件进⾏加载,向⼦组件传递。当出现跨多层级数据传递的时候,还是相对麻烦。如果我们想实现⾮⽗⼦组件之间数据通信,如上图红线连接的组件所示,虽然也可以逐级传递,但是很麻烦。
  • 举个例⼦:A、B、C 三个组件都需要根据应⽤的⽤户数据 U 改变其数据显示效果及显示内容,那么⽤户数据 U 应该定义在哪个组件⾥⾯呢?好像定义在哪⾥都不合适。
  • 如果 A 组件改变了⽤户数据 U ,组件 B 和 C 的如何知道数据 U 发⽣了变化并作出响应,实现数据的状态 ⼀致性?

三、状态是什么?

我们通常把应⽤各组件、各模块之间的公共数据叫做“状态”。

  • 在开发 web 应⽤时,常把⼀些各个模块都会⽤到的公共数据保存到 session ⾥⾯,这些公共数据就是状态。⽐如:⽤户登录状态信息。
  • 我们在写⼀段代码的时候,常常把⼀些多段代码都使⽤的公共变量定义抽取出来,⽤于判断执⾏逻辑。这些公共变量也是状态。

状态数据有两个特点:⼀是数据的共享,⼆是数据的变更将导致影响的产⽣。状态由于其共享性,导致其⼀旦发⽣变化,就会对引⽤它的模块或组件产⽣影响。所以通常伴随着“状态”,还有“状态管理”。⽽ Vuex 就是⼀个实现组件之间状态共享和状态变更管理的 Vue 插件。

四、Vuex 状态管理

官⽅定义:Vuex 是⼀个专为 Vue.js 应⽤程序开发的状态管理模式。它采⽤集中式存储管理应⽤的所有组件的状态,并以相应的规则保证状态以⼀种可预测的⽅式发⽣变化。

  • 状态集中式状态管理:简单地说就是将多个组件中的公共数据,单独抽取到⼀个公共对象内进⾏存 储,并且这个公共存储对象是⼀个单例。
  • Vuex 另外⼀个显著的作⽤就是实现公共状态的响应式编程。公共状态数据 State 的变化导致引⽤该状态的视图组件 View 发⽣变化,View 视图的⽤户输可以触发 Actions 动作,Actions 动作⼜可以改变公共状态 State。

五、Vuex 使⽤场景

  • Vue 的 SPA 单⻚⾯应⽤组件规模较⼤的时候,出现⼤量的组件之间有数据通信的时候。
  • Vue 组件之间出现数据传递及响应式编程困难的时候。
  • Vue 多组件依赖于同⼀个状态,或者不同视图⾏为需要改变同⼀状态的时候。

2.Vuex 的第⼀个例⼦

⼀、本节⼤纲

  • Vuex 插件的安装
  • 计数器组件基础代码
  • Vuex 集中化存储

⼆、Vuex 插件的安装

在 Vue 项⽬⽬录下执⾏如下命令:

npm install vuex --save

安装完成之后,在 package.json 的项⽬配置⽂件中会显示出 vuex 的安装版本。⼿动在项⽬的 src ⽬录下创建 strore ⽂件夹和 index.js ⽂件,index.js ⽂件内容如下。该⽂件夹内的内容就是 Vuex 进⾏状态集中管理的“仓库”。

import Vue from "vue";
import Vuex from "vuex";
//使⽤Vuex插件
Vue.use(Vuex);
//注意这⾥创建的是store对象,不是vuex对象
const store = new Vuex.Store({
  state: {},
  mutations: {},
  actions: {},
  getters: {},
  modules: {},
});
//导出对象
export default store;

从上图中可以看到,在 store 对象中我们定义了⼀系列的⼦对象 state、mutations、actions、getters、 modules ,这就是我们学习 Vuex 的主要内容。

我们建好了“仓库”,还得把它引⼊到项⽬⾥⾯来。在 main.js 中加⼊如下代码:

import store from "./store";
//将store加⼊vue实例
new Vue({
  ...store, //加这⾥
  render: (h) => h(App),
}).$mount("#app");

这样 Vuex 就安装完成了,在项⽬⾥⾯就可以使⽤了。

三、计数器组件基础代码

我们定义两个组件、⼀个组件 name 为 VuexCpnOne,另⼀个组件 name 为 VuexCpnTwo。代码完全相同,如下:

<template>
  <div>
    <div>计数值:{{ counter }}</div>
    <!-- 1. couter值不同步 -->
    <button @click="counter++">+1</button>
    <button @click="counter--">-1</button>
  </div>
</template>
<script>
  export default {
    name: "VuexCpnOne",
    data() {
      return {
        counter: 0,
      };
    },
  };
</script>
<style></style>

我们定义的两个组件,除了名字不同,其他的代码完全⼀致。代码中定义了⼀个数据变量 counter。

然后我们将这两个组件引⼊到同⼀个⽗组件⾥⾯,也就是说这两个组件是兄弟组件。⽗组件 App.vue 的代码:

<template>
  <div id="app">
    <vuex-cpn-one></vuex-cpn-one>
    <hr />
    <vuex-cpn-two></vuex-cpn-two>
  </div>
</template>
<script>
  import VuexCpnOne from "./components/VuexCpnOne.vue";
  import VuexCpnTwo from "./components/VuexCpnTwo.vue";
  export default {
    name: "App",
    components: {
      VuexCpnOne,
      VuexCpnTwo,
    },
  };
</script>
<style>
  #app {
    font-family: Avenir, Helvetica, Arial, sans-serif;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    text-align: center;
    color: #2c3e50;
    margin-top: 60px;
  }
</style>

最终的实现效果如下:

两个组件的 counter 的值不同步,因为 counter 是分别定义在两个组件⾥⾯的,从程序运⾏的⻆度来讲, counter 是⼀个局部变量。

如果我们希望点击组件 VuexCpnOne 的按钮,同步影响组件 VuexCpnTwo 的 counter 值;点击组件 VuexCpnTwo 的按钮,同步影响组件 VuexCpnOne 的 counter 值。该怎么做?

⽐较麻烦的做法就是:点击 VuexCpnOne 按钮,向⽗组件传递点击事件,⽗组件向 VuexCpnTwo 传递 counter 数据。还有⼀种简单的做法就是将 counter 变成⼀个全局变量。

四、Vuex 集中化存储

那么我们怎么让 counter 变成⼀个全局变量?答案就是使⽤ vuex,将 counter 定义在 store/index.js 中的 store 对象的 state 状态对象⾥⾯。

const store = new Vuex.Store({
  state: {
    counter: 0,
  },
});

然后修改 VuexCpnOne 组件和 VuexCpnTwo 组件的代码如下:

<template>
  <div>
    <div>计数值:{{$store.state.counter}}</div>
    <button @click="$store.state.counter++">+1</button>
    <button @click="$store.state.counter--">-1</button>
  </div>
</template>
<script>
export default {
  name:'VuexCpnOne'
}
</script>
  • $store 代表项⽬全局的集中存储的 store 对象,即 store/index.js 中定义的 store 对象
  • store 对象中的 state 属性,⽤于定义全局变量,多个组件都可以使⽤该变量

实现效果:⽆论我们点击哪⼀个组件的加 1 减 1 按钮,counter 的值在两个组件⾥⾯的显示都是同步的⼀致的。由此我们可以知道$store.state.counter 状态是响应式的,即:我们点击组件 VuexCpnOne 的按钮,组件 VuexCpnTwo 中的 counter 也发⽣变化。

直接修改 state 变量值虽然可以做到组件的响应式编程,但这种做法仍然是不好的做法。

3.mutations 的使⽤与状态跟踪

⼀、本节⼤纲

  • mutations 的作⽤
  • mutations 的基本使⽤
  • vue devtools 状态跟踪
  • mutations ⽅法携带参数
  • mutations 常量

⼆、mutations 的作⽤

在上⼀节我们通过⼀个简单的双组件 VuexCpnOne 和 VuexCpnTwo 的例⼦,使⽤ Vuex 实现了计数状态

  • 当点击任何⼀个组件中的按钮,实际上调⽤了 $store.state.counter++$store.state.counter--,就修改了公共状态 counter 计数的值,从⽽影响另⼀个组件的显示内容同步。
  • 直接对$store.state 中的状态进⾏赋值操作,是可以实现响应式的,即:state 的变化影响所有引⽤ 它的视图的变化。

但是存在⼀个问题:我们没有办法进⾏状态跟踪,也就是我们只能看到状态的结果,⽆法知道状态改变的过程。⽐如: 我们想知道经过⼏次点击,counter 的值变成了 3,第⼀次点击按钮后 counter 的值是什么,第⼆次点击后 counter 的值是什么。

可能有⼈会说,这很好操作啊,点⼀下看⼀下变化就好了,但是我们只是两个组件引⽤了公共的 state 中的⼀个 counter 变量。如果很多个组件引⽤很多的公共状态 state,该如何跟踪每⼀次点击按钮操作之后的 state 变化?如何留下痕迹?

直接对 $store.state 中的状态进⾏赋值操作是⽆法留痕的,我们需要使⽤ mutation(改变)。

mutation 是实际上就是对 state 状态进⾏操作的⾃定义⽅法,通过触发 mutations ⽅法修改的 state 可以留痕,可以被 vue devtools 调试⼯具跟踪。

但是注意不要在 mutation ⾃定义⽅法⾥⾯进⾏异步操作,⽐如发送⽹络请求。因为异步回调⽆法有效的被 mutation 跟踪,所以mutation ⾃定义⽅法⾥⾯必须是同步操作

三、mutations 的基本使⽤

在上⼀节双组件 VuexCpnOne 和 VuexCpnTwo 的例⼦的基础上进⾏代码修改,⾸先定义 mutations :

const store = new Vuex.Store({
...
  state:{
    counter: 0
 },
  mutations:{
    add(state){
      state.counter++
   },
    sub(state){
      state.counter--
   }
 },
...
})

mutations ⾃定义⽅法的第⼀个参数是就是 state,我们可以通过 state 参数修改状态。然后在组件中使⽤ $store.commit() ⽅法触发 mutation ,commit 的第⼀个参数是 mutation 的⽅法名。如下:

<button @click="$store.commit('add')">+1</button>
<button @click="$store.commit('sub')">-1</button>

视图效果和使⽤ $store.state.counter++$store.state.counter-- 是⼀样的。但是使⽤ mutation 改变的 state 会留痕,可以被跟踪。

四、vue devtools 状态跟踪

vue devtools 是专⻔⽤于 vue 开发调试的⼯具,在各个浏览器中以扩展程序的形式出现,参考各浏览器安装扩展程序的⽅法⾃⾏安装⼀下。

使⽤⽅法:调出浏览器的开发者⼯具,可以看到 vue 选项。可以对组件、路由、vuex 等进⾏⽅便地查看和调试。

五、 mutations ⽅法携带参数

现在我们变化⼀下需求,希望每点击⼀次按钮加 5 减 5 或加 n 减 n,不再是加 1 减 1。这样我们就希望 mutation ⾃定义的⽅法能够传参,这样我们就能够针对 state 状态做更灵活的操作,适应更⼴泛的需求。

mutations:{
    add(state,num){
      state.counter =  state.counter + num
   },
    sub(state,num){
      state.counter = state.counter - num
   }
},

双组件计数器的例⼦,我们触发 mutation 的时候,是这样的代码:

<button @click="$store.commit('add',5)">+5</button>
<button @click="$store.commit('sub',5)">-5</button>

mutations 除了 state 只能传递⼀个参数,这个参数有个专有名词叫做 payload。

只有⼀个参数,那我们希望传递多组数据的时候怎么办?可以将它们封装到⼀个对象⾥⾯。所以 payload 可以是⼀个基础的数据类型,也可以是⼀个对象。

payload 对象代码如下:

mutations:{
    add(state,payload){
      state.counter =  state.counter + payload.num  * payload.multiple
   },
    sub(state,payload){
      state.counter = state.counter - payload.num * payload.multiple
   }
},

通过为 mutation ⽅法传递 payload 对象,实现加减 5 的 2 倍,即加减 1

<button @click="$store.commit('add',{num:5,multiple:2})">+5</button>
<button @click="$store.commit('sub',{num:5,multiple:2})">-5</button>

六、mutations 常量

通过上⾯的例⼦,⼤家可能注意到:

  • 那就是 mutation 函数⼀次定义,在多个组件内多次 commit 调⽤。
  • 触发 mutation 的⽅法 commit 的第⼀个参数,就是 mutation 函数的名称。

那么,我们在开发中就有可能遇到⼀个问题:如果有开发⼈员修改了 mutation 函数的⽅法名,那么我们如何保证对应的 commit ⽅法的参数⼀也对应的进⾏修改?⽐较笨的⽅式就是字符串查找,然后⼀个⼀个 改。还有⼀种情况就是:通过查找的⽅式⼀个⼀个改,漏掉了怎么办?这种可能性很⼤。所以我们在⼀开始就要避免这个问题:将 mutation 函数名称定义为常量。新建⼀个⽂件叫做 store/mutation-types.js

定义常量

export const COUNTER_ADD = "add";

在 mutation 函数定义的时候,先导⼊ mutation-types,再使⽤ [] 引⽤常量

import {
  COUNTER_ADD
} from './mutation-types'
const store = new Vuex.Store({
  state:{
    counter: 0
 },
  mutations:{
   [COUNTER_ADD] (state,payload){
      state.counter = state.counter + payload.num * payload.multiple
   },
    ...
 }
})

在调⽤ commit 触发 mutation 的时候,同样先导⼊ mutation-types,再使⽤常量。注意通过 js 模块导⼊的 COUNTER_ADD 不能再 html ⾥⾯被使⽤,所以我们需要单独定义 method ,在 method 中使⽤常量。

import { COUNTER_ADD } from "../store/mutation-types";
export default {
  name: "VuexCpnTwo",
  methods: {
    addCounter() {
      this.$store.commit(COUNTER_ADD, { num: 5, multiple: 2 });
    },
  },
};
<button @click="addCounter()">+5</button>

4.全局计算属性 getters

⼀、本节⼤纲

  • 英⽂ fullName 需求基础实现
  • v-model 绑定 state 状态数据的标准做法
  • Vuex 的 getters 的定义与使⽤

⼆、英⽂ fullName 需求基础实现

英美等地的⼈的名字通常分为 firstName 和 lastName ,⼆者的组合是全名 fullName 。类似于我们的姓和名。store.state 的数据定义如下:

state:{
    firstName:"",
    lastName:""
},

在 CpnThree 组件中 input 输⼊ firstName 和 lastName,并通过 v-model 指令将属性值与 state 状态变量进⾏绑定。并显示 fullName 全名,fullName 是 firstName 和 lastName 的组合。VuexCpnThree 组件代码如 下:

<template>
  <div>
    <label for="firstName">firstName:</label>
    <input type="text" v-model="$store.state.firstName" id="firstName" />
    <label for="lastName">lastName:</label>
    <input type="text" v-model="$store.state.lastName" id="lastName" />

    <div>VuexCpnThree组件</div>
    <div>fullName:{{$store.state.firstName }}-{{$store.state.lastName }}</div>
  </div>
</template>

在 VuexCpnFour 组件中也显示 fullName 全名。VuexCpnFour 组件代码如下:

<template>
  <div>
    <div>VuexCpnFour组件</div>
    <div>fullName:{{$store.state.firstName }}-{{$store.state.lastName }}</div>
  </div>
</template>

将上⾯的两个组件,引⽤到同⼀个⽗组件⾥⾯,最后的显示结果如下:

  • 当我们在第⼀个组件⾥⾯输⼊ firstName 和 lastName 的时候,另⼀个组件 fullName 也随之发⽣变化。
  • 使⽤ v-model 指令绑定了 $store.state.firstName$store.state.lastName 状态数据。
  • 我们看到上⾯代码中的 fullName 是通过 {{$store.state.firstName }}-{{$store.state.lastName }} 拼接⽽成的,在两个组件⾥⾯分别进⾏计算得出 fullName。

三、v-model 绑定 state 状态数据的标准做法

前⾯我们使⽤ v-model 绑定了 $store.state 状态数据,实现了输⼊框与 state 状态数据之间的绑定,但是不推荐这种做法。因为这种直接的绑定⽅式,状态⽆法被 devtools 跟踪。

浏览器显示上已经为 zhang-san ,但是调试⼯具中的 firstName 和 lastName 仍然是空串。⽐较正规的做法是:v-model 绑定计算属性。

<label for="firstName">firstName:</label>
<input type="text" v-model="firstName" id="firstName" />

然后在 computed 计算属性的 get 和 set ⽅法⾥⾯进⾏ state 变量的状态管理。

computed: {
  firstName:{
    get(){
      return this.$store.state.firstName
   },
      set(newVal){
        this.$store.commit('handleFirstNameVal', newVal)
     }
 }
},

在 mutation 是⾥⾯定义 handleFirstNameVal,对 firstName 状态赋值。这种⽅式虽然较上⼀⼩节的实现麻烦了很多,但确实是标准的做法。这样做完之后 firstName 状态数据就可以正确的被 devtools s 所跟踪。

handleFirstNameVal(state,payload){
      state.firstName = payload
}

四、vuex 的 getters 的定义与使⽤

在上⾯的实现中,我们看到全名 fullName 的值是通过 {{ $store.state.firstName }}-{{ $store.state.lastName }} 拼接⽽成的,在两个组件⾥⾯分别使⽤。还有⼀种⽅式就是将 firstName 和 lastName 先计算出 fullName,然后在组件⾥⾯使⽤ fullName,这种⽅法就是 getters。

state:{
    firstName:"",
    lastName:""
},
getters:{
    fullName(state){
        console.log(state.firstName +"-"+ state.lastName);
        return state.firstName + "-" + state.lastName
   }
},

下⾯代码显示效果和使⽤ {{ $store.state.firstName }}-{{ $store.state.lastName }}是⼀样的。

<div>fullName:{{$store.getters.fullName}}</div>

说明:使⽤ getters 对 state 数据进⾏计算,不管有多少组件引⽤了 getters,getters 都只计算⼀次并且对计算结果进⾏缓存,后续的 getters 被组件调⽤都是⽤缓存结果。

只要 state 数据不发⽣变化,缓存结果就不发⽣变化。这与 computed 计算属性的表现是⼀致的,所以只要理解了 conputed 计算属性,getters 就是针对 state 的全局计算属性,这样就很容易使⽤和理解。

5.Vuex 状态异步操作

⼀、本节⼤纲

  • Mutation 为什么不能做异步操作
  • Action 基本⽤法
  • 结合 Promise 使⽤ Action

⼆、Mutation 为什么不能做异步操作

我们尝试⼀下在 mutation 中定义⼀个异步操作,并做⼀个实验。代码如下

mutations:{
  submitBtn(state){
    //setTimeout异步操作
    setTimeout(() => {
      state.firstName = "zhang"
   }, 1000);
 }
},

然后在组件中 commit 触发这个异步操作

<label for="firstName">firstName:</label>
<input type="text" v-model="firstName" id="firstName" />

<button @click="$store.commit('submitBtn')">触发异步操作</button>

点击“触发异步操作”显示结果如下:界⾯上的 firstName 已经变成了 zhang ,但是 devtools 跟踪的结果 firstName 却仍然是⼀个空串。也就是说在 Mutation 的异步操作中,devtools ⽆法正确的跟踪 state 状态。

所以 Mutation 中只能定义同步操作,异步操作交给 Action。

三、Action 基本⽤法

  • 在组件中使⽤ dispatch 触发异步操作 Action(如⽹络请求 backend API,后端服务 API )
  • 在异步操作 Action 中对 Mutation 操作进⾏ commit
mutations:{
  submitBtn(state){
    //setTimeout(() => {//这⾥不能做异步操作
    state.firstName = "zhang"
    //}, 1000);
 }
},
  actions:{
    submitAction(context){
      setTimeout(() => {//异步操作
        //state数据的修改还是由mutation执⾏
        context.commit('submitBtn');
     }, 1000);
   }
},
  • action 的⽅法的参数 context 意为上下⽂,在我们还没有学习 vuex 的 module 之前可以暂且认为它是 $store 对象。我们后⽂再做详解。
  • 最后我们可以在组件中通过 dispatch ⽅法触发 Action。这样的操作结果就是: state.firstName 在异步操作中也可以被 devtools 跟踪状态。
<label for="firstName">firstName:</label>
<input type="text" v-model="firstName" id="firstName" />

<button @click="$store.dispatch('submitAction')">触发异步操作</button>

四、结合 Promise 使⽤ Action

我们已经知道了 Vuex 的 Action ⽤来执⾏异步操作。假设有这样⼀个需求,希望在组件中发起异步操作 (如:⽹络请求),并在组件中针对⽹络请求的结果数据进⾏处理(⽽不是在 Action 或 mutation 中)。 这就要使⽤到 Promise。

  • 在 Action 操作中⽤ Promise 包裹异步操作
  • 将 Promise 作为 Action 的返回值
  • 在组件中⽤返回的 Promise 对象进⾏ then ⽅法操作
actions:{
  submitAction(context,payload){
    return new Promise((resolve,reject) =>{
      setTimeout(() => {
        context.commit('submitBtn');
        resolve("异步操作完成了" + payload)
     }, 1000);
   })
 }
},

action ⽅法同样可以使⽤ payload 传参,传参的⽅式与 mutation ⼀致。

我们修改⼀下 action 的触发⽅式以及回调数据处理:

<button @click="demoAction()">触发异步操作</button>

methods: {
    demoAction(){
      this.$store.dispatch('submitAction',"demoAction")
           .then(data =>{
              console.log(data)
           })
   }
},
  • 我们向 action 传递了⼀个参数:字符串”demoAction”。
  • 在异步操作中使⽤ resolve 函数,说明 Promise 异步操作被正确执⾏。并将异步操作结果返回。
  • ⽤ promise 的 then ⽅法接收异步请求的结果,此处针对结果只做简单的 log 打印处理:“异步操作完成了 demoAction。”

附加:对象的解构赋值

actions:{
    submitAction(context){
      setTimeout(() => {//异步操作
        context.commit('submitBtn');
     }, 1000);
   }
},

换⼀种写法,仔细对⽐代码。

actions:{
    submitAction({commit,state,getters}){
      setTimeout(() => {//异步操作
        commit('submitBtn');
     }, 1000);
   }
},
  • ⾸先 context 是⼀个上下⽂对象,我们可以暂且认为它是 $store 对象。
  • $store 对象解构,使⽤三个对象 commit,state,getters 分别进⾏解构赋值。

6.modules 模块划分

⼀、本节⼤纲

  • 引⼊ Modules 的意义
  • 如何组织 Module 代码
  • 模块外部引⽤模块内的状态
  • 模块内引⽤全局的状态

⼆、引⼊ Modules 的意义

  • Vuex 进⾏集中状态管理,可以认为它是单例模式。
  • 当应⽤变的⾮常复杂的时候,所有的状态都定义在⼀个 store 对象⾥⾯会显得⼗分的臃肿。
  • 为此,Vuex 提供了从语法的⻆度将 store 分割为多个模块的⽅法。每个模块可以拥有⾃⼰的 state、 mutations、actions、getters 。虽然区分了模块,但是状态仍然是集中管理的,也同样⽀持响应式状态管理。

三、如何组织 Module 模块代码

四、模块外部引⽤模块内的状态

对于如下的 A 模块定义的变量及状态该如何被外部引⽤调⽤?

const moduleA = {
  state: { counter: 0 },
  mutations: {
    increment(state) {
      // 这⾥的 `state` 对象是模块内的局部状态
      state.counter++;
    },
  },
  getters: {
    doubleCount(state) {
      return state.counter * 2;
    },
  },
};
  • ⾸先我们要明确⼀点,这⾥的 state ( mutation 和 getters 参数)不再是全局的 state,⽽是模块内部的局部的模块的 state。
  • 模块外部正确的引⽤模块内状态的⽅法是:$store.state.a.counter ,⽽不是 $store.state.counter
  • 但是,模块外部调⽤ mutations 仍然是 $store.state.commit ,⽽不是 $store.state.a.commit 。 getters、actions 同理。

五、模块内引⽤全局的状态

const moduleA = {
  actions: {
    incrementRootSum({ state, commit, rootState, rootGetters }) {
      if ((state.count + rootState.count) % 2 === 1) {
        commit("increment");
      }
    },
  },
  getters: {
    sumWithRootCount(state, getters, rootState) {
      return state.count + rootState.count;
    },
  },
};
  • rootState 代表全局定义注册的状态变量,rootGetters 代表全局定义的 getters。注意前缀 root 在模块内代表全局的,不带 root 前缀的都是模块内局部定义的。可以使⽤⼆者在模块内部使⽤全局定义。
  • 上⾯代码 state 代表模块内部定义的状态变量,getters 参数代表模块内部的局部定义的 getters。
  • 还要注意⼀点,我们之前讲过 action ⽅法的参数 context,在没有模块之前我们可以认为 context 是全局对象 store ,在模块内 context 代表当前模块 Module 本身。上⾯代码中引⽤了 context 当前模块的{ state,commit,rootState, rootGetters }

六、store ⽂件⽬录组织

现在所有的状态及状态管理代码都是写到⼀个⽂件夹 store 的 index.js ⾥⾯,随着项⽬的扩⼤,这样不利于书写、查找及管理。通常我们需要将这些代码进⾏ js 的模块化管理。如:ES6 的模块化、CommonJS 模块化等。

store |
  --index.js | //模块组装导出⼦⽂件
  --actions.js | //根级别的actions
  --mutations.js | //根级别的mutations
  --getters.js | //根级别的getters
  --modules |
  --moduleA.js | //模块A状态管理⽂件
  --moduleB.js; //模块B状态管理⽂件

将 actions、mutations、getters、modules 代码写到单独的⽂件并 export 导出,在 index.js 中 import 导⼊。最终形成的⽬录结构如上所示,请参考上⽅⽬录结构的。

6.1.全局的状态管理⽂件拆分

我们⾸先创建三个根级别的⽂件 actions.js,mutations.js ,getters.js ,这三个⽂件⽤于定义全局的状态管理代码。以 mutations.js 为例,代码如下(因为我们还没有做任何的定义,所以先导出空对象):

export default {
  //在这⾥定义mutations
};

然后在 store/index.js 中将它们导⼊并使⽤⽣效。

6.2.分模块的状态管理

  • ⾸先新建⼀个⽂件夹 store/modules ,⽤于存放分模块的状态管理信息。
  • 假如我们需要管理登录⽤户的状态信息,我们新建⼀个 store/modules/sysuser.js ⽂件。内容如 下:可以在其内部定义与“⽤户状态”相关的状态管理代码。最后导出。

文章作者: Syhan
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Syhan !
评论
  目录