如何使用Vuex管理Vue.js应用程序中的状态
作者选择 Open Sourcing Mental Illness 作为 Write for DOnations 计划的一部分来接受捐赠。
介绍
Vuex是Vue.js的第一方开发状态管理库。 它由 Evan You 创建,目前由 Vue.js 核心团队维护。 与许多其他状态管理库一样,Vuex 遵循 Redux 在过去几年中流行的原则:数据流向一个方向,动作和突变在称为存储的单一事实来源中修改数据。
Vuex store 是不同方法和数据的集合。 其中一些方法,例如 actions,可以在数据发送到突变之前获取和处理数据。 mutation 是一种使用提供的值改变或更新 store 属性的方法。 Getters 是可以修改或组合数据以创建新状态属性的方法。 这些 getter 是只读的,不会改变数据。 这些类似于 Vue.js 组件 中的计算属性。 Vuex 的最后一个组件是 state,或充当您的单一事实来源的数据集。
在本教程中,您将创建一个应用程序来呈现包含机场信息的卡片列表。 单击时,这些卡片将执行 Vuex 工作流程,将所选机场添加到收藏夹列表中。 通过运行此示例,您将执行操作和变更来管理状态和获取计算数据的方法。
先决条件
- Node.js 版本
14.16.0
或更高版本安装在您的计算机上。 要在 macOS 或 Ubuntu 20.04 上安装它,请按照 如何在 macOS 上安装 Node.js 和创建本地开发环境中的步骤或 的 使用 PPA 部分安装如何在 Ubuntu 20.04 上安装 Node.js - Vue CLI 在您的机器上安装 并生成了一个新项目。 确保在生成应用程序时选择
Default (Vue 3 Preview)
选项。 该项目的名称将是favorite-airports
,它将作为根目录。 - 您还需要 JavaScript、HTML 和 CSS 的基本知识,您可以在我们的 如何使用 HTML 构建网站系列、如何使用 CSS 构建网站 系列中找到这些知识, 以及 如何在 JavaScript 中编码 。
第 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
、abbreviation
、city
和 state
属性的卡片。 当用户点击一张卡片时,您将执行一个 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。 状态是给定时间的数据集合。 可以通过 dispatch
和 commit
方法通过用户交互更改此状态。 当用户修改数据时,会执行一个 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
(调度调用突变),和 getters
(store
计算属性)。
保存 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 商店的名字和姓氏。 这些分别是 firstName
和 lastName
的默认值。
在这一步中,您安装了 Vuex 并创建了一个 Vuex 商店。 您添加了一些默认存储数据,并使用点概念将其与 $store
对象一起显示在视图中。 在下一步中,您将通过 actions
和 mutations
更新您的 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}` } } })
您在这里使用 模板文字 将 firstName
和 lastName
放入一个字符串中。
保存此文件,然后移回 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_FAVORITE
和 REMOVE_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
,其值为 true
。 namespace
属性将使您可以在稍后使用点符号访问属性时引用模块名称。
在此对象内部,您将添加与用户关联的 state
和 getter
信息:
最喜欢的机场/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
文件并删除 state
、mutations
、actions
和 getters
属性。 您的文件将类似于以下代码段:
最喜欢的机场/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
以引用每个模块中的状态和调度事件。
结论
在高层次上,状态管理就是更新数据。 在此设置中,状态中的数据在整个应用程序中是全局的,并充当单一事实来源,只能使用动作和突变形式的显式函数进行更新。 在本教程中,您浏览了 state
、mutations
、actions
和 getters
的示例,并了解了这些属性在更新周期。
要了解有关 Vuex、动作、突变和模块的更多信息,请查看 Vue.js 核心团队编写的 官方 Vuex 文档。 有关 Vue 的更多教程,请查看 如何使用 Vue.js 开发网站系列页面。