如何使用Vuex管理Vue.js应用程序中的状态

来自菜鸟教程
跳转至:导航、​搜索

作者选择 Open Sourcing Mental Illness 作为 Write for DOnations 计划的一部分来接受捐赠。

介绍

VuexVue.js的第一方开发状态管理库。 它由 Evan You 创建,目前由 Vue.js 核心团队维护。 与许多其他状态管理库一样,Vuex 遵循 Redux 在过去几年中流行的原则:数据流向一个方向,动作和突变在称为存储的单一事实来源中修改数据。

Vuex store 是不同方法和数据的集合。 其中一些方法,例如 actions,可以在数据发送到突变之前获取和处理数据。 mutation 是一种使用提供的值改变或更新 store 属性的方法。 Getters 是可以修改或组合数据以创建新状态属性的方法。 这些 getter 是只读的,不会改变数据。 这些类似于 Vue.js 组件 中的计算属性。 Vuex 的最后一个组件是 state,或充当您的单一事实来源的数据集。

在本教程中,您将创建一个应用程序来呈现包含机场信息的卡片列表。 单击时,这些卡片将执行 Vuex 工作流程,将所选机场添加到收藏夹列表中。 通过运行此示例,您将执行操作和变更来管理状态和获取计算数据的方法。

先决条件

第 1 步 — 设置示例应用程序

为了帮助可视化如何使用 Vuex 管理状态,请设置一个项目,其中包含一些要在视图中显示的数据。 您将在整个教程中使用该项目。

按照 Prerequisites 部分中的说明创建 favorite-airports 项目后,创建一个目录来保存该项目的所有本地数据。 打开终端并在项目根目录(favorite-airports)中运行以下命令:

mkdir src/data
touch src/data/airports.js

这将在其中创建 data 目录和一个空的 airports.js 文件。

在您选择的文本编辑器中,打开新创建的 airports.js 文件并添加以下内容:

最喜欢的机场/src/data/airports.js

export default [
  {
    name: 'Cincinnati/Northern Kentucky International Airport',
    abbreviation: 'CVG',
    city: 'Hebron',
    state: 'KY'
  },
  {
    name: 'Seattle-Tacoma International Airport',
    abbreviation: 'SEA',
    city: 'Seattle',
    state: 'WA',
  },
  {
    name: 'Minneapolis-Saint Paul International Airport',
    abbreviation: 'MSP',
    city: 'Bloomington',
    state: 'MN',
  },
  {
    name: 'Louis Armstrong New Orleans International Airport',
    abbreviation: 'MSY',
    city: 'New Orleans',
    state: 'LA',
  },
  {
    name: `Chicago O'hare International Airport`,
    abbreviation: 'ORD',
    city: 'Chicago',
    state: 'IL',
  },
  {
    name: `Miami International Airport`,
    abbreviation: 'MIA',
    city: 'Miami',
    state: 'FL',
  }
]

这是一个由 objects 组成的 array,由美国的几个机场组成。 在此应用程序中,您将遍历此数据以生成包含 name abbreviationcitystate 属性的卡片。 当用户点击一张卡片时,您将执行一个 dispatch 方法,该方法会将那个机场添加到您的 Vuex 状态中作为最喜欢的机场。

保存data/airports.js并返回终端。

完成该步骤后,创建一个名为 AirportCard.vue 的单文件组件 (SFC)。 该文件将位于项目的 components 目录中。 该组件将包含机场卡的所有样式和逻辑。 在您的终端中,使用 touch 命令创建 .vue 文件:

touch src/components/AirportCard.vue

在文本编辑器中打开 AirportCard.vue 并添加以下内容:

最喜欢的机场/src/components/AirportCard.vue

<template>
  <div class="airport">
    <p>{{ airport.abbreviation }}</p>
    <p>{{ airport.name }}</p>
    <p>{{ airport.city }}, {{ airport.state }}</p>
  </div>
</template>

<script>
export default {
  props: {
    airport: {
      type: Object,
      required: true
    }
  }
}
</script>

<style scoped>
.airport {
  border: 3px solid;
  border-radius: .5rem;
  padding: 1rem;
  margin-bottom: 1rem;
}

.airport p:first-child {
  font-weight: bold;
  font-size: 2.5rem;
  margin: 1rem 0;
}

.airport p:last-child {
  font-style: italic;
  font-size: .8rem;
}
</style>

您可能会注意到此代码片段中包含一些 CSS。 在 AirportCard.vue 组件中,包装器 <div> 包含 airport 类。 这个 CSS 通过添加边框为生成的 HTML 添加一些样式,使每个机场看起来像一张“卡片”。 :first-child:last-child 选择器,它们对 div 内 HTML 中的第一个和最后一个 p 标记应用不同的样式具有 airport 的类。 除此之外,您可能还注意到该组件包含一个 prop,它在 Vue.js 中是一种将数据从父组件向下传递到子组件的方法。

保存并退出文件。

在结束设置之前,将现有的 App.vue 组件替换为以下代码:

最喜欢的机场/src/App.vue

<template>
  <div class="wrapper">
    <div v-for="airport in airports" :key="airport.abbreviation">
      <airport-card :airport="airport" />
    </div>
  </div>
</template>

<script>
import { ref } from 'vue'
import allAirports from '@/data/airports.js'
import AirportCard from '@/components/AirportCard.vue'

export default {
  components: {
    AirportCard
  },
  setup() {
    const airports = ref(allAirports)
    return { airports }
  }
}
</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;
}

.wrapper {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-column-gap: 1rem;
  max-width: 960px;
  margin: 0 auto;
}

p,
h3 {
  grid-column: span 3;
}
</style>

此代码包含一个 v-for 循环,它遍历 airports.js 数据并渲染一系列 AirportCards.vue 组件,其中机场数据通过 prop [X172X ]。 保存此代码并返回命令行。

设置好项目后,在终端中使用 npm run serve 命令运行本地开发服务器:

npm run serve

这将在您的 localhost 上启动一个服务器,通常在端口 8080 上。 打开您选择的网络浏览器并访问 localhost:8080 以查看以下内容:

现在您的示例应用程序已设置完毕,下一步您将安装 Vuex 库并创建一个 store。 这个商店是许多不同的 Vuex 项目的集合,包括:状态、突变、动作和 getter。 为了说明这一点,您将执行调度方法,这会将机场添加到应用程序的收藏夹部分。

第 2 步 — 安装 Vuex

在处理基于 Web 的应用程序时,您通常会使用 state。 状态是给定时间的数据集合。 可以通过 dispatchcommit 方法通过用户交互更改此状态。 当用户修改数据时,会执行一个 dispatch 事件,将数据传递给 mutation 并更新 state 对象。

有几种方法可以处理更新状态。 一些开发者跳过actions,直接进入mutatations。 但是,为了本教程的目的,您将始终执行 action,然后调用 mutation。 这样你就可以在一个动作中进行多个突变。 Vuex 的基本规则是,mutations 有一项工作,而且只有一项工作:更新存储。 动作可以做许多不同的事情,包括组合数据、获取数据和运行 JavaScript 逻辑。

除了actions,还有getters。 getter 是一种将多个状态值组合成单个值的方法。 如果您熟悉 Vue.js 中的计算属性,则可以将 getter 视为特定于状态的计算属性。

了解 Vuex 术语后,开始安装和集成 Vuex。 打开终端并运行以下命令:

npm install vuex@next --save

此命令将安装与 Vue.js 3.x 最兼容的 Vuex 版本,并将其保存在您的 package.json 文件中。 接下来,为您的商店创建一个目录和一个索引文件。 您将使用 mkdir 命令创建一个目录并使用 touch 创建一个新文件:

mkdir src/store
touch src/store/index.js

打开您的文本编辑器并在您的 store/index.js 文件中,初始化您的 Vuex 存储。 为此,您需要利用 Vuex 的 createStore 函数:

机场收藏夹/src/store/index.js

import { createStore } from 'vuex'

export default createStore({
  
})

你也可以 export 这个,因为你稍后会将它导入到你的 main.js 文件中。

此时,您已经设置了 Vuex 商店,但应用程序还没有意识到它或如何使用它。 要完全初始化存储,请将 import 放入您的 main.js 文件中。 在您的文本编辑器中,打开 src/main.js 文件。

createApp(App) 之后立即链接 use 方法并传递到您正在导入的商店中,如以下突出显示的代码所示:

最喜欢的机场/src/main.js

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'

createApp(App).use(store).mount('#app')

链接 use 方法后,保存此文件。 use 方法告诉 Vue 应用程序在构建应用程序时将哪些代码捆绑在一起。 在这种情况下,您是在告诉 Vue “使用”或捆绑 Vuex 商店。

在继续下一部分之前,将一个状态值添加到您的存储中并在 App.vue 文件中引用它。 打开 store/index.js 文件并添加以下对象和值:

最喜欢的机场/src/store/index.js

import { createStore } from 'vuex'

export default createStore({
state: {
    firstName: 'John',
    lastName: 'Doe'
  },
mutations: {

},
actions: {

},
getters: {

}
})

这些属性反映了存储所保存的数据类型:state 用于状态(全局数据),mutations(提交改变数据),actions(调度调用突变),和 gettersstore 计算属性)。

保存 store/index.js,然后在文本编辑器中打开 App.vue 文件并添加以下内容:

最喜欢的机场/src/App.vue

<template>
  <div class="wrapper">
    <p>{{ $store.state.firstName }} {{ $store.state.lastName }}</p>
    <div v-for="airport in airports" :key="airport.abbreviation">
      <airport-card :airport="airport" />
    </div>
  </div>
</template>
...

本例中的 $store 是您在 main.js 文件中初始化的全局存储。 如果您将 this.$store 登录到控制台,您将看到商店 object。 从那里,代码通过 dot notation 访问您要显示的属性。

保存 App.vue 然后打开您的网络浏览器。 在机场卡片上方,您将看到您保存到 Vuex 商店的名字和姓氏。 这些分别是 firstNamelastName 的默认值。

在这一步中,您安装了 Vuex 并创建了一个 Vuex 商店。 您添加了一些默认存储数据,并使用点概念将其与 $store 对象一起显示在视图中。 在下一步中,您将通过 actionsmutations 更新您的 Vuex 存储,您将获得与 getters 的组合数据。

第 3 步——创建动作、突变和吸气剂

在第 2 步中,您手动安装了 Vuex 并将其集成到您的项目中。 在这一步中,您仍然可以在浏览器中呈现名字和姓氏,但您将创建一个 Vuex getter 来将数据呈现为一个字符串。 如前所述,您可以将 Vuex getter 视为 Vuex 存储的计算属性。

要创建 getter,请在您选择的文本编辑器中打开 src/store/index.js 文件。 打开后,在 getters 对象中创建一个属性,并将函数作为其值。 属性的名称是您稍后访问 getter 的方式。

添加以下突出显示的代码:

最喜欢的机场/src/store/index.js

import { createStore } from 'vuex'

export default createStore({
  state: {
    firstName: 'John',
    lastName: 'Doe'
  },
  ...
  getters: {
    fullName: function () {
      
    }
  }
})

在这种情况下,您将使用该函数来组合名字和姓氏,并将结果属性存储为 fullName。 在函数内部,您需要传入 Vuex 存储中的 state 对象。 从那里,返回一个插入了名字和姓氏的字符串:

最喜欢的机场/src/store/index.js

import { createStore } from 'vuex'

export default createStore({
  state: {
    firstName: 'John',
    lastName: 'Doe'
  },
  ...
  getters: {
    fullName: function (state) {
      return `${state.firstName} ${state.lastName}`
    }
  }
})

您在这里使用 模板文字firstNamelastName 放入一个字符串中。

保存此文件,然后移回 App.vue。 在此文件中,删除第一个和最后一个值并用 getter 替换它们:

最喜欢的机场/src/App.vue

<template>
  <div class="wrapper">
    <p>{{ $store.getters.fullName }}</p>
    <div v-for="airport in airports" :key="airport.abbreviation">
      <airport-card :airport="airport" />
    </div>
  </div>
</template>
...

进行此更改并保存文件后,您的浏览器将 热重载 。 您将像以前一样在浏览器中看到名字和姓氏,但现在您正在利用 getter。 如果您更改 Vuex 商店中的名称之一,getter 将自动更新。

从吸气剂继续前进,有 actions。 如上一步所述,就本教程而言,您将始终使用操作而不是直接更改数据。

在此项目中,您将在用户单击卡片时将机场数据添加到“收藏夹”列表中。 您将首先创建操作和突变,然后使用 v-on 指令将其分配给单击事件。

要创建动作,请在文本编辑器中打开 src/store/index.js 文件。 在商店的 actions 部分中,创建一个函数。 与 getter 一样,函数名称将是您稍后引用动作的方式。 将此函数命名为 addToFavorites

最喜欢的机场/src/store/index.js

import { createStore } from 'vuex'

export default createStore({
  state: {
    firstName: 'John',
    lastName: 'Doe',
    favorites: [] // will store favorites here
  },
  mutations: {
  
  },
  actions: {
    addToFavorites() {
      
    }
  },
  getters: {
    fullName: function (state) {
      return `${state.firstName} ${state.lastName}`
    }
}
})

一个动作接受两个参数:context 或 Vue 应用程序本身,以及 payload 或您要添加到存储中的数据。 上下文有一个与之关联的 commit 方法,您将使用它来调用稍后将进行的突变:

最喜欢的机场/src/store/index.js

import { createStore } from 'vuex'

export default createStore({
  state: {
    firstName: 'John',
    lastName: 'Doe',
    favorites: []
  },
  mutations: {
  
  },
  actions: {
    addToFavorites(context, payload) {
      context.commit('UPDATE_FAVORITES', payload)
    }
  },
  getters: {
    fullName: function (state) {
      return `${state.firstName} ${state.lastName}`
    }
 }
})

commit 方法还接受两个参数:要调用的突变的名称和 payload 或突变将替换状态的数据。

在此代码中,您将变异命名为 UPDATE_FAVORITES。 突变名称应该是不可知的,而不是以特定动作命名的。 例如,像 ADD_FAVORITEREMOVE_FAVORITE 这样的突变意味着一个逻辑,比如删除或添加一条数据。 这并不理想,因为突变应该只有一项工作且只有一项工作:更新状态。 为了区分添加和删除数据,您可以有两个不同的 actions 从数组中删除或添加最喜欢的机场,然后执行一个名为 UPDATE_FAVORITES 的单一突变,用任何内容更新数组通过了。 最大限度地减少存储中的突变数量将有助于使您的 Vuex 存储更易于管理,因为它的复杂性和规模越来越大。

接下来,为这个动作添加一些逻辑。 当您将机场添加为“收藏夹”时,您会将该有效载荷(机场数据)添加到现有数组中。 为此,您可以使用 JavaScript 中的 push 方法:

最喜欢的机场/src/store/index.js

import { createStore } from 'vuex'

export default createStore({
  state: {
    firstName: 'John',
    lastName: 'Doe',
    favorites: []
  },
  mutations: {
  
  },
  actions: {
    addToFavorites(context, payload) {
      const favorites = context.state.favorites
      favorites.push(payload)
      context.commit('UPDATE_FAVORITES', favorites)
    }
  },
  getters: {
    fullName: function (state) {
      return `${state.firstName} ${state.lastName}`
    }
 }
})

此时,您的操作设置为将 payload 添加到您的 favorites 数组,然后使用变异数组作为新数据调用变异。 接下来,您将定义 UPDATE_FAVORITES 突变。 添加以下代码将 favorites 数组设置为新数组:

最喜欢的机场/src/store/index.js

import { createStore } from 'vuex'

export default createStore({
  state: {
      firstName: 'John',
    lastName: 'Doe',
    favorites: []
    },
  mutations: {
    UPDATE_FAVORITES(state, payload) {
      state.favorites = payload
    }
  },
  actions: {
    addToFavorites(context, payload) {
      const favorites = context.state.favorites
      favorites.push(payload)
      context.commit('UPDATE_FAVORITES', favorites)
    }
  },
  getters: {
    fullName: function (state) {
      return `${state.firstName} ${state.lastName}`
    }
 }
})

现在你有了你的动作和你的突变,你可以保存这个文件。

要执行此操作,您可以在用户单击卡片时调用 dispatch 事件。 您将使用 v-on 指令执行此操作。

在文本编辑器中打开 App.vue 文件。 在 <airport-card /> 组件上,添加 v-on 指令简写语法 (@),事件为 click

最喜欢的机场/src/App.vue

<template>
  <div class="wrapper">
    <p>{{ $store.getters.fullName }}</p>
    <div v-for="airport in airports" :key="airport.abbreviation">
      <airport-card :airport="airport" @click="$store.dispatch('addToFavorites', airport)" />
    </div>
    <h2 v-if="$store.state.favorites.length">Favorites</h2>
    <div v-for="airport in $store.state.favorites" :key="airport.abbreviation">
      <airport-card :airport="airport"  />
    </div>
  </div>
</template>
...

dispatch 函数接受两个参数:动作名称和您发送给动作的有效负载数据。

保存此文件并在浏览器中打开它。 现在,当您单击机场卡时,该操作将调用更新状态并将机场添加到收藏夹属性的突变。

在这一步中,您扩展了之前创建的 Vuex 商店。 您创建了一个复制数组并将新项目推送到该数组的操作。 该动作称为突变,进而更新状态。 除此之外,您还了解了 getters 以及如何通过组合或修改 Vuex 存储中的只读值来利用它们来创建新属性。

在最后一步中,您将实现 Vuex 模块。 模块是将 Vuex 商店拆分为更小的 Vuex 商店的好方法。 这对于规模和复杂性更大的 Vuex 商店种植者很有用。

第 4 步——编写 Vuex 模块

模块是较小的 Vuex 存储,它们组合成一个 Vuex 存储。 这类似于如何将多个 Vue.js 组件导入单个 .vue 文件,例如 App.vue。 在这一步中,您将把这个 Vuex 存储分成两个独立的模块。 一个模块将用于 user 状态,另一个将特定于 airport 状态、动作和突变。

在您的终端中,将 cd 放入 store 目录并使用 touch 命令创建两个单独的文件。

touch src/store/user.module.js
touch src/store/airports.module.js

user.module.js 文件中,通过添加以下代码创建一个默认导出的对象:

最喜欢的机场/src/store/user.module.js

export default {
  namespaced: true
}

您还将添加属性 namespaced,其值为 truenamespace 属性将使您可以在稍后使用点符号访问属性时引用模块名称。

在此对象内部,您将添加与用户关联的 stategetter 信息:

最喜欢的机场/src/store/user.module.js

export default {
  namespaced: true,
  state: {
    firstName: 'John',
    lastName: 'Doe'
  },
  getters: {
    fullName: function (state) {
      return `${state.firstName} ${state.lastName}`
    }
  }
}

用户模块包含用户信息所需的一切。 保存并退出文件。

继续在 airports.module.js 文件中做同样的事情。 在文本编辑器中打开 airports.module.js 文件并添加以下内容:

最喜欢的机场/src/store/airports.module.js

export default {
  state: {
    favorites: []
  },
  mutations: {
    UPDATE_FAVORITES(state, payload) {
      state.favorites = payload
    }
  },
  actions: {
    addToFavorites(context, payload) {
      const favorites = context.state.favorites
      favorites.push(payload)
      context.commit('UPDATE_FAVORITES', favorites)
    }
  },
}

现在您已经放置了 airport 相关的突变、动作和状态,您可以保存您的 airports.module.js

接下来,将import这两个文件放到主store/index.js文件中。 在您的文本编辑器中,打开 store/index.js 文件并删除 statemutationsactionsgetters 属性。 您的文件将类似于以下代码段:

最喜欢的机场/src/store/index.js

import { createStore } from 'vuex'

export default createStore({

})

要注册模块,您需要使用以下突出显示的代码将它们导入到此 index.js 文件中:

最喜欢的机场/src/store/index.js

import { createStore } from 'vuex'
import UserModule from './user.module.js'
import AirportsModule from './airports.module.js'

export default createStore({

})

从这里开始,您将需要一个名为 modules 的属性,其中一个对象作为其值。 该对象内部的属性名称将是 Vuex 模块的名称。 模块名称的值是导入的模块本身:

最喜欢的机场/src/store/index.js

import { createStore } from 'vuex'
import UserModule from './user.module.js'
import AirportsModule from './airports.module.js'

export default createStore({
modules: {
  user: UserModule,
  airports: AirportsModule
}
})

保存此文件后,您的模块现在已注册并合并到您的单个 Vuex 存储中。 保存 store/index.js,然后打开 App.vue 文件并更新它以引用新创建的模块:

最喜欢的机场/src/App.vue

<template>
  <div class="wrapper">
    <p>{{ $store.getters['user/fullName'] }}</p>
    <div v-for="airport in airports" :key="airport.abbreviation">
      <airport-card :airport="airport" @click="$store.dispatch('addToFavorites', airport)" />
    </div>
    <h2 v-if="$store.state.airports.favorites.length">Favorites</h2>
    <div v-for="airport in $store.state.airports.favorites" :key="airport.abbreviation">
      <airport-card :airport="airport"  />
    </div>
  </div>
</template>
...

现在你有了一个模块化版本的 Vuex 设置。

在这一步中,您将现有的 Vuex 存储分割成更小的块,称为模块。 这些模块是将相关存储属性分组到较小的 Vuex 存储中的好方法。 您还更新了 App.vue 以引用每个模块中的状态和调度事件。

结论

在高层次上,状态管理就是更新数据。 在此设置中,状态中的数据在整个应用程序中是全局的,并充当单一事实来源,只能使用动作和突变形式的显式函数进行更新。 在本教程中,您浏览了 statemutationsactionsgetters 的示例,并了解了这些属性在更新周期。

要了解有关 Vuex、动作、突变和模块的更多信息,请查看 Vue.js 核心团队编写的 官方 Vuex 文档。 有关 Vue 的更多教程,请查看 如何使用 Vue.js 开发网站系列页面