十四、使用 Vuex 和发送 GET HTTP 请求来简化状态管理
本章是关于发送 HTTP 请求和解决大型 web 应用中最常见的问题——同步一个组件与另一个组件的状态的问题。 在大型和复杂的应用中,您需要一个工具来集中应用的状态,并使数据流透明和可预测。
在本章中,我们将涵盖以下主题:
- 理解复杂的状态管理
- 发送 HTTP 请求
- 使用 Vuex 设置状态管理
技术要求
你需要 Visual Studio Code 来完成本章。
本章已完成的知识库可以在https://github.com/PacktPublishing/ASP.NET-Core-and-Vue.js/tree/master/Chapter14找到。
了解复杂状态管理
在开发大型复杂的 Angular、React 或 Vue web 应用时,你会遇到状态管理。 那么状态管理是什么意思呢?
应用状态管理是当你的应用从一个或几个视图开始成长。 您可能会开始遇到这样的问题:您希望在不同的嵌套组件之间共享应用的一些状态。 举个例子,你必须创建一个机制,让两个深度组件始终同步。 见图 14.1图:
图 14.1 -共享组件的本地状态
图 14.1显示组件 Y与组件 X状态相同。 有很多方法可以做到。 一种是绕过事件,从顶级组件传入属性。 尽管您可以传递道具和事件,但在多个嵌套组件中传递事件和道具会使应用难以维护,也会使代码难以推理。
了解全局状态
那么,对于由于多个嵌套组件之间的状态共享而导致的不可维护的代码,该如何解决呢? 应用范围状态或全局状态,也称为,即存储。 全局状态解决方案的思想是,每个视图和每个组件都可以只是一个中心状态的反映。 见图 14.2:
图 14.2 -无响应的全局状态
图 14.2 显示了一个活跃的全局状态,它可以被任何组件访问。 拥有一家商店是件好事。 我们只有一个真理的来源,而一切都只是它的反映。 全局状态或存储的活动性是确保用户在应用中看到的所有内容始终保持同步的好方法。
那么如何在 Vue.js 中建立一个商店呢? 我们稍后再做,但让我们在下一节中做一些不带存储的简单数据获取。 简单的数据获取将证明我们可以向我们的 ASP 发送一个 HTTP GET 请求.NET Core Web API。
在 Vue.js 中发送 HTTP 请求
如果您正在开发现代 web 应用,那么将 HTTP 请求发送到 RESTFul 服务是很简单的。 HTTP 客户端库有api-sauce
、super-agent
和axios
。 你知道 JavaScript 本身有一个用于发送 HTTP 请求的本地 API 吗? 是的,这是 Fetch API。
Fetch API 可以在 JavaScript 和 TypeScript 中使用。 然而,它只在现代浏览器中工作,当您在旧浏览器中加载应用时将不起作用。 不仅如此,在我看来,Fetch API 在使用它的 API 发送请求时也可能过于冗长。 我更喜欢一个带有抽象的 HTTP 客户端库,而不是编写额外的代码,比如res.json()
、headers
或property
方法。 在这种情况下,我们将使用 Axios 作为 HTTP 客户端库。
Axios 是一个基于承诺的 HTTP 客户端库,用于浏览器和服务器端 Node.js 应用。它使用简单,也支持旧浏览器。 我们将把 Axios 和 Day.js 一起安装,这是一个用于操作日期和时间的库。
因此,转到您的vue-app
根目录并运行以下npm
命令:
npm i axios dayjs
前面的npm
命令将安装axios
和dayjs
。
接下来,我们在src
目录中创建一个名为api
的文件夹。
在api
文件夹中创建一个 JavaScript 文件api-v1-config.js
,并编写以下代码:
import axios from "axios";
const debug = process.env.NODE_ENV !== "production";
const baseURL = debug
? "https://localhost:5001/api/v1.0/"
: "https://traveltour.io/api/v1.0/";
let api = axios.create({ baseURL });
export default api;
前面的代码是针对 ASP 的 API 版本之一的 Axios 设置.NET Core Web API。 我们正在创建一个 Axios 实例,并将一个基 URL 传递给它使用。
让我们在api
文件夹中创建另一个 JavaScript 文件api-v2-config.js
,并编写如下代码:
import axios from "axios";
const debug = process.env.NODE_ENV !== "production";
const baseURL = debug
? "https://localhost:5001/api/v2.0/"
: "https://traveltour.io/api/v2.0/";
let api = axios.create({ baseURL });
export default api;
前面的代码是针对我们的 ASP 的 API 版本的一个 Axios 配置.NET Core Web API。 你会注意到这里唯一不同的是版本。
在api
文件夹中创建另一个 JavaScript 文件weather-forecast-services.js
,并编写以下代码:
import api from "@/api/api-v2-config";
export async function getWeatherForecastV2Axios(city) {
return await api.post(`WeatherForecast/?city=${city}`);
}
前面的代码是用于向WeatherForecast v2
控制器发送 POST 请求的服务。 我们将 Axios 配置的默认导出命名为api
,然后使用它调用WeatherForecast
端点的POST
方法。 我喜欢在函数服务中添加axios
后缀,以便在阅读 IDE 或代码编辑器的智能感知时帮助我识别要导入的正确文件。
现在让我们更新views
|AdminDashboard
文件夹的WeatherForecast.vue
组件。 用以下代码更新文件夹的内容:
<script>
import { getWeatherForecastV2Axios } from "@/api/weather-
forecast-services";
export default {
name: "WeatherForecast",
async mounted() {
await getWeatherForecastV2Axios("Oslo");
},
};
</script>
在前面的代码中,我们导入了getWeatherForecastV2Axios
服务,并在mounted()
生命周期钩子中使用它,以便在 DOM 呈现之前提前触发它。
通过在Travel.WebApi
项目中运行以下命令来运行后端应用和前端应用:
dotnet run
运行前面的dotnet
命令后等待几秒钟,然后进入浏览器,输入https://localhost:5001
,检查 Chrome 的 DevTool,如下截图所示:
图 14.3 -向天气预报 API 发送 POST 请求后 Chrome DevTool 的状态
图14.3POST 请求返回一个【T6 状态】200 OK 代码,这意味着我们得到我们所要求的,那就是下面的 JSON 结果:****
图 14.4 -向天气预报 API 发送 POST 请求后的 JSON 响应
WeatherForecast
控制器的 JSON 响应如图图 14.4所示。 响应表示我们可以发送请求并从后端获得响应。
现在我们知道浏览器中有数据可以用于用户界面,让我们为数据构建一个 UI。
让我们再次更新WeatherForecast.vue
。
让我们从我们安装的dayjs
库中导入relativeTime
和dayjs
:
import relativeTime from "dayjs/plugin/relativeTime";
import dayjs from "dayjs";
relativeTime
将日期格式化为相对时间字符串,如an hour ago
或in 2 days
。
现在让我们更新挂载的生命周期钩子:
async mounted() {
this.loading = true;
dayjs.extend(relativeTime);
await this.fetchWeatherForecast(this.selectedCity);
this.loading = false;
},
我们使用库dayjs
来操作日期。 您还会注意到,我们正在使用本地状态loading
,将其设置为 true,然后在获取数据后将其设置为 false。 我们将使用本地状态loading
在应用的屏幕上显示一个旋转器组件。
现在让我们定义我们的局部状态:
data() {
return {
weatherForecast: [],
cities: [],
selectedCity: "Oslo",
loading: false,
};
},
我们在本地状态中有一个weatherForecast
数组、一个cities
数组、一个selectedCity
字符串和一个loading
布尔值。 它们都是用默认值初始化的。
让我们在methods
对象中添加一个异步方法,如下所示:
methods: {
async fetchWeatherForecast(city = "Oslo") {
this.loading = true;
try {
const { data } = await getWeatherForecastV2Axios(
city);
console.log(data);
this.weatherForecast = data?.map((w) => {
const formattedData = { ...w };
let date = w.date;
formattedData.date = dayjs(date).fromNow();
return formattedData;
});
} catch (e) {
alert("Something happened. Please try again.");
} finally {
this.loading = false;
}
},
},
我们在这个函数中将旋转器loading
设置为true
,使用getWeatherForecastV2Axios
服务发送请求。 在获得数据之后,我们将使用 JavaScript 数组实用程序map
,它就像一个循环,从 JSON 数组响应格式化每个对象的日期。
让我们还创建另一个方法来返回颜色编码的温度。 在 GitHub repo 中获得完整的函数,因为它太大了,无法在这里写入:
getColor(summary) {
switch (summary) {
case "Freezing":
return "indigo";
// get the rest from the github
default:
return "grey";
}
},
该方法是本质上只是一个助手,根据温度返回颜色。
现在来看一下template
语法。 这里是:
<template>
<v-container>
<div class="text-h4 mb-10">
Two-week weather forecast of different cities
</div>
<div class="v-picker--full-width d-flex justify-center"
v-if="loading">
<v-progress-circular
:size="70"
:width="7"
color="purple"
indeterminate
></v-progress-circular>
</div>
<!-- Insert <v-simple-table> here -->
</v-container>
</template>
前面的代码是应用的title
、container
和WeatherForecast
页面的微调器 UI。
现在将v-simple-table
组件包含在WeatherForecast
UI 的容器中。 将组件插入到你会找到<!-- Insert <v-simple-table> here -->
注释的地方:
<v-simple-table>
<template v-slot:default>
<thead>
<tr>
<th class="text-left">Dates</th>
<th class="text-left">City</th>
<th class="text-left">℃</th>
<th class="text-left">℉</th>
<th class="text-left">Summary</th>
</tr>
</thead>
<tbody>
<tr v-for="item in weatherForecast"
:key="item.date">
<td>{{ item.date }}</td>
<td>{{ item.city }}</td>
<td>{{ item.temperatureC }}</td>
<td>{{ item.temperatureF }}</td>
<td>
<v-chip :color="getColor(item.summary)" dark>{{
item.summary
}}</v-chip>
</td>
</tr>
</tbody>
</template>
</v-simple-table>
其中的v-simple-table
项为WeatherForecast
表,其中显示了奥斯陆某一特定日期的温度:
图 14.5 -两周天气预报
以上预报为Oslo的两周预报。 在那里你可以看到日期、城市、摄氏度和华氏温度,以及天气的可视化总结。
在下一节中,我们将添加一个下拉选择器,我们可以在其中选择一个城市,我们可以选择的城市将来自我们的 ASP.NET Core Web API。
现在我们有了一个使用简单 HTTP 请求而不使用存储的WeatherForecast
特性。 我们将在下一节中构建的特性将使用一个 Vuex 状态管理库。 我们将在另一个组件重用的功能,需要一个存储在下一章,第 15 章,发帖子,删除,把 HTTP 请求在 Vue.js Vuex【显示】。
使用 Vuex 设置状态管理
Vuex是 Vue.js 的官方状态管理库,广泛用于管理复杂组件。 Vuex 有一个被动的全球商店,设置起来相当容易。 在编写代码时,我将解释 Vuex 实现的各个部分。
但是在我们开始构建我们的存储之前,让我们先从 API 控制器中删除授权,这样我们在向api/v1.o/
端点发送请求时就不需要认证令牌了。
为此,转到Travel.WebApi
项目的Travel.WebApi.Controllers.v1
名称空间中的ApiController.cs
文件,并注释Authorize
属性,如下所示:
// [Authorize]
在注释了Authorize
属性之后,我们现在可以暂时使用api/v1.0/
。
让我们从设置 Vuex 的更新部分开始。
第一步-写一个商店
在store
文件夹中创建一个名为tour
的文件夹。 它将像这样:src
|store
|tour
。
第二步-编写模块
在tour
文件夹中创建一个名为services.js
的 JavaScript 文件,并编写如下代码:
import api from "@/api/api-v1-config";
export async function getTourListsAxios() {
return await api.get("TourLists");
}
我们正在创建一个向版本 1TourLists
控制器或端点发送请求的服务。
第 3 步-如果我们使用 TypeScript,编写一个模块
创建一个名为types.js
的 JavaScript 文件,仍然在tour
文件夹中,然后编写以下代码:
export const LOADING_TOUR = "LOADING_TOUR";
export const GET_TOUR_LISTS = "GET_TOUR_LISTS";
前面的代码不是编写 Vuex 时的必要条件,而是在实现状态管理时成为设计的一部分。 前面的类型是,称为动作类型,我们访问这些类型是为了让存储知道它在接收到动作后应该在状态中采取什么样的动作。 蛇形的类型只是字符串,但是它们可以在写入操作时防止拼写错误,稍后您将看到。
第 4 步-编写 API 服务
在tour
文件夹中创建一个名为actions.js
的 JavaScript 文件,然后编写如下代码:
import * as types from "./types";
import { getTourListsAxios } from "@/store/tour/services";
// asynchronous action using Axios
export async function getTourListsAction({ commit }) {
commit(types.LOADING_TOUR, true);
try {
const { data } = await getTourListsAxios();
commit(types.GET_TOUR_LISTS, data.lists);
} catch (e) {
alert(e);
console.log(e);
}
commit(types.LOADING_TOUR, false);
}
该操作包含如何更新存储的全局状态的说明。 Action 是在其他 JavaScript 框架的状态管理库中也可以找到的术语。 因此,记住这一点很有价值。
此外,如果需要,操作可以包含异步操作。 我们定义了一个带有解构的commit
参数的action
函数来进行突变。
要使用提交,只需将操作类型作为第一个参数传递。 如果在特定的提交中需要一个有效载荷,我们可以使用第二个可选参数来传递有效载荷。 一个例子就是commit(types.LOADING_TOUR, true);
。 在前面的句子中,这行代码是一个关于用布尔参数作为动作的payload
加载tour
的commit
语句。
我们并不局限于在一个action
函数中编写一个提交。 只要需要,我们可以编写一个或多个提交。
我们在actions.js
的getTourListAction
中所做的是启用loading tour
。 我们的目标是在使用getTourListsAxios
服务获取数据时创建一个微调器。 然后,我们将使用commit(types.GET_TOUR_LISTS, data.lists)
中的响应将数据保持在全局状态。
然后通过false
到loading tour
停止纺纱器的旋转。
在后面的章节中,当我们写更多的动作时,你会看到更多。
第五步-写一个动作类型
在tour
文件夹中创建一个名为state.js
的 JavaScript 文件,然后编写如下代码:
const state = {
lists: [],
loading: false,
};
export default state;
state
对象将成为应用的全局状态的一部分,也就是商店。 存储是一个具有默认值或初始值的属性的大对象。 您必须记住初始值或初始状态,因为它们也适用于不同 JavaScript 框架中的任何其他状态管理库。
第六步-写一个动作
在tour
文件夹中创建另一个名为mutations.js
的 JavaScript 文件,然后编写如下代码:
import * as types from "./types";
const mutations = {
[types.GET_TOUR_LISTS](state, lists) {
state.lists = lists;
},
[types.LOADING_TOUR](state, value) {
state.loading = value;
},
};
export default mutations;
突变是函数,根据被分派的动作的类型获得触发器。 它们执行实际的状态修改。 正如您在前面的代码中看到的,types.GET_TOUR_LISTS
更改了state.lists
的值。 参数state
是第一个参数,它是全局状态的一部分。 它会自动被维克斯通过。
第二个参数是来自我们前面定义的操作的可选负载。
另外,突变的工作是直接更新全局状态的一部分。 这取决于是否要删除或更新现有状态。
步骤 7 -写一个状态
在tour
文件夹中创建一个名为getters.js
的 JavaScript 文件,然后编写如下代码:
const getters = {
lists: (state) => state.lists,
loading: (state) => state.loading,
};
export default getters;
getter是用于存储的计算属性或派生值。 getters
的结果或输出基于其依赖项进行缓存,当其任何依赖项发生更改时将重新运行或重新计算。 我们将在组件中包含getters
,以便将全局状态连接到 UI。
步骤 8 -写一个突变
在tour
文件夹中创建一个名为index.js
的 JavaScript 文件。 这个文件将在我们的tour
模块的索引文件中。 文件创建后,我们编写以下代码:
import state from "./state";
import getters from "./getters";
import mutations from "./mutations";
import * as actions from "./actions";
export default {
namespaced: true,
getters,
mutations,
actions,
state,
};
我们导入tour
模块或名称空间的index
文件的state
文件、getters
文件、mutations
文件和actions
文件。 在导入必要的文件后,我们将导出它们和,其中包括一个namespaced
属性设置为true
。
模块或名称空间是存储中的分区。 每个模块或名称空间都有其actions
、state
、mutations
、getters
,甚至还有嵌套模块。
第 9 步-写一个 getter
用下面的代码更新存储的index.js
文件。 代码是我们将要添加tour
模块的地方:
import Vue from "vue";
import Vuex from "vuex";
import createLogger from "vuex/dist/logger";
import tourModule from "./tour";
Vue.use(Vuex);
const debug = process.env.NODE_ENV !== "production";
const plugins = debug ? [createLogger({})] : [];
export default new Vuex.Store({
modules: {
tourModule,
},
plugins,
});
我们导入一个记录器,前面文件中的 tour 模块,然后删除以下属性:state
、mutations
和actions
。 然后我们使用tourModule
作为modules
对象的属性。
modules
对象是 Vue.js 应用中所有模块的位置。 这意味着当我们在 Vue.js 应用中添加新模块或名称空间时,我们将更新modules
对象。
步骤 10 -通过插入模块更新存储
现在让我们更新WeatherForecast.vue
页面。 同样,让我们从vuex
中导入mapActions
和mapGetters
:
import { mapActions, mapGetters } from "vuex";
mapGetters
是一个助手,它简单地将本地计算属性映射到存储getters
。 类似地,mapActions
是一个助手,它将存储分派映射到组件方法。 我们将在methods
块和computed
块中使用这些映射器。
插入mapActions
到methods
块的第一行中。 使用spread
操作符,以mapActions
为前缀的三个圆点如下:
methods: {
...mapActions("tourModule", ["getTourListsAction"]),
…
},
mapActions
将自动创建的local
方法this.getTourListsAction
映射到this.$store.dispatch("getTourListsAction")
。 我们现在可以使用this.getTourListsAction
来触发突变的动作。
在使用getTourListsAction
之前,我们还希望访问存储的lists
状态。 要做到这一点,创建computed
块,并使用扩展操作符插入mapGetters
,如下所示:
computed: {
...mapGetters("tourModule", {
lists: "lists",
}),
},
mapGetters
将自动创建的本地状态this.lists
映射到this.$store.getters.lists
。
第 11 步-使用 mapgetter 和 mapActions 更新组件
让我们用下面的代码来更新方法。 我们将添加两行新代码:
async mounted() {
…,
await this.getTourListsAction();
this.cities = this.lists.map((pl) => pl.city);
},
我们触发getTourListsAction
,然后从列表中提取城市,并将它们存储在城市的本地状态中。
现在,对于WeatherForecast
屏幕的下拉框或选择UI,在template
语法区域的v-simple-table
组件上添加v-select
组件:
<v-select
@change="fetchWeatherForecast"
v-model="selectedCity"
:items="cities"
label="City"
persistent-hint
return-object
single-line
clearable
></v-select>
前面的组件将为WeatherForecast
屏幕提供功能,我们可以在其中选择查看哪个城市的 14 天天气预报。
在运行 ASP.NET Core 和 Vue.js 应用,我想让你使用的 SQLite 数据库的Travel.WebApi
项目。 我已经更新了其中的数据,以帮助我们了解我们在前端的设计方面正在做什么。 您将看到 JSON 相当于 SQLite 数据在 GitHub 回购https://github.com/PacktPublishing/ASP.NET-Core-5-and-Vue.js-3/tree/master/Chapter-14,让你知道日期目前在数据库中,它的形状。
删除项目中现有的TravelTourDatabase.sqlite3
数据库文件,并将其替换为 GitHub 存储库中的文件。
通过在Travel.WebApi
项目中运行以下命令重新运行应用:
dotnet run
等待几秒钟,然后检查浏览器。 你应该看到下拉菜单选择器如下图所示:
图 14.6 - Vuetify 选择组件
图 14.6 显示了选择器,默认的Oslo作为所选城市。 尝试点击它来查看其他的可用城市。
接下来,检查浏览器的 DevTools 上的控制台日志。 您还应该看到我们在商店设置中包含的 Vuex 记录器插件。 它看起来类似于图 14.7 所示:
Figure 14.7 – Vuex logger
前面截图中的 Vuex 记录器显示了 Vue.js 应用的 Vuex 状态管理中发生的事情的日志。 如果在使用 Vuex 商店的部件时应用中出现意外行为,我们可以使用它来调试应用。
在其他框架中使用 Vuex 和其他状态管理库需要大量代码来设置它们。 但是一旦你完成了所有移动部分的设置和你的商店的配置,添加新的动作和使用它们就变得很容易了,因为你只需要更新现有的代码。 您只需要编写一次设置,其余的都是无缝的。
尽管完成设置需要时间,但是不必在嵌套组件内外传递道具和事件的好处非常好,这将使您在开发嵌套组件之间的复杂状态同步时更加轻松。
我建议记住前面的主题流,无论何时在项目中实现 Vuex,都要有一个清单,说明要做什么。 现在让我们回顾一下你在本章所学的内容。
总结
由于状态管理的概念,本章可能是迄今为止最具挑战性的章节之一。 然而,学习如何进行状态管理是非常宝贵的。 您已经学习了如何使用 Axios 发送 HTTP 请求。 您已经在大型应用中发现了状态管理的思想。
您还学习了如何使用 Vuex 和 Vuex 的部分,例如存储、模块、操作、突变和 getter。
在下一章中,我们将构建在 Vue.js 中使用 Vuex 发送POST、DELETE、和PUT HTTP请求的功能。
版权属于:月萌API www.moonapi.com,转载请注明出处