介绍
在构建利用 Internet 的应用程序时,请务必记住,并非每个用户都会拥有与开发人员相同的连接速度。在本地机器上工作的开发人员不会拥有与咖啡店甚至家中的最终用户相同的体验。重要的是要记住,一些用户可能认为您的应用程序的某些部分已损坏,仅仅是因为互联网连接不够快!
虽然我们无法控制互联网速度,但我们可以做出相应的计划并防止用户遇到应用程序损坏的情况。幸运的是,Vue 3 提供了一种处理此类情况的新方法,称为 Suspense。“Suspense”是 Vue 中一个新的内置组件,我们可以将另一个需要在渲染之前执行异步操作的组件包裹起来。Suspense 在 Vue 中的实现与React Suspense非常相似。如果内部组件<Suspense></Suspense>具有异步setup()方法,则会向用户呈现回退直到完成。
<Suspense> Component
让我们探索一个使用 Suspense 的基本示例。我们将构建一个基本应用程序,该应用程序利用Pokemon API来获取浆果列表并在下拉列表中显示它们。首先,我们有两个文件,App.vue并且Berries.vue:
Berries.vue
<template>
<h1 class="text-3xl pb-2">Select a Berry</h1>
<select v-model="selectedBerry" class="px-4 py-2 w-40 shadow">
<option value="" disabled>Select...</option>
<option
v-for="berry in berries.results"
:key="berry.url"
:value="berry.url"
>
{
{ berry.name }}
</option>
</select>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import useBerries from "../hooks/useBerries";
export default defineComponent({
name: "Berries",
async setup() {
const selectedBerry = ref("");
const berries = await useBerries();
return { berries, selectedBerry };
},
});
</script>
App.vue
<template>
<Suspense>
<Berries />
<template #fallback>
<span class="text-3xl">Picking berries...</span>
</template>
</Suspense>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import Berries from "./components/Berries.vue";
export default defineComponent({
name: "App",
components: {
Berries,
},
});
</script>
我们来看看这两个文件。
在Berries.vue
中,我们有一个异步setup()
方法,它返回两个值:selectedBerry
和berries
。在本例中,berries
它是由自定义函数创建的useBerries
,但在底层,它是对 Pokemon API 的常规 HTTP 请求,以获取浆果列表。API 请求的确切实现对于我们的示例并不重要。然后在模板中照常引用这些值。
如果其中任何一个看起来令人困惑,我建议您查看
这篇关于使用“ref”和“reactive”的文章 或这篇解释一般组合 API 的文章。
在中,我们照常App.vue
导入并注册它。Berries.vue
然而,在模板中,我们使用<Suspense>
组件。请记住,它内置在 Vue 3 中,因此无需在任何地方注册。在 Suspense 中,我们有 Berries 组件和一个名为 的模板fallback
。在 Berries.vue中的setup
方法返回其承诺之前,将显示回退中的内容。返回后,将setup
呈现默认模板(在本例中为 Berries 组件)。
让我们更进一步,为我们的应用程序添加一些功能。当用户从下拉列表中选择浆果时,我们希望请求提供的 URL 并显示浆果的味道。为此,我们将添加另一个组件BerryDetails.vue
,并在 中使用 Suspense Berries.vue
。
以下是更改:
BerryDetails.vue
<template>This berry is {
{ berryFlavor.flavor.name }}.</template>
<script lang="ts">
import { defineComponent } from "vue";
import useBerryFlavor from "../hooks/useBerryFlavor";
export default defineComponent({
name: "BerryDetails",
props: {
url: {
type: String,
required: true,
},
},
async setup(props) {
const berryFlavor = await useBerryFlavor(props.url);
return {
berryFlavor,
};
},
});
</script>
Berries.vue
<template>
<h1 class="text-3xl pb-2">Select a Berry</h1>
<select v-model="selectedBerry" class="px-4 py-2 w-40 shadow">
<option value="" disabled>Select...</option>
<option
v-for="berry in berries.results"
:key="berry.url"
:value="berry.url"
>
{
{ berry.name }}
</option>
</select>
<!-- Add section to display BerryDetails -->
<div class="w-3/5 m-auto p-5">
<Suspense v-if="selectedBerry">
<BerryDetails :url="selectedBerry" />
<template #fallback> Fetching berry details... </template>
</Suspense>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from "vue";
import useBerries from "../hooks/useBerries";
import BerryDetails from "./BerryDetails.vue";
export default defineComponent({
name: "Berries",
async setup() {
const selectedBerry = ref("");
const berries = await useBerries();
return { berries, selectedBerry };
},
components: {
BerryDetails,
},
});
</script>
BerryDetails.vue
非常简单——它显示一个带有浆果味道的字符串。它还接受一个 prop url
,它是一个字符串。这个 URL 被传递到一个自定义方法中useBerryFlavor
(同样,发出 API 请求的实现对我们的示例并不重要)。
Berries.vue
更新为包含一个 Suspense 块,看起来与App.vue
. 这里唯一的区别是我们在渲染之前等待selectedBerry
被设置为一个值,这是有道理的;如果我们没有选择的浆果,我们不想发出 API 请求。
错误处理
伟大的!我们的应用程序已启动并正在运行,任何缓慢都会向用户反映,以便他们知道应用程序正在运行。正确的?嗯,差不多。如果这些 API 请求之一引发错误怎么办?我们需要将这一点传达给用户,而不是让应用程序永远挂起。在这种情况下,Vue 3 为我们提供了一个名为 的钩子onErrorCaptured
,我们可以使用它来捕获错误并更新显示。让我们看一下我们更新的Berries.vue
组件:
Berries.vue
<template>
<h1 class="text-3xl pb-2">Select a Berry</h1>
<select v-model="selectedBerry" class="px-4 py-2 w-40 shadow">
<option value="" disabled>Select...</option>
<option
v-for="berry in berries.results"
:key="berry.url"
:value="berry.url"
>
{
{ berry.name }}
</option>
</select>
<div class="w-3/5 m-auto p-5">
<!-- Added error handling block -->
<div v-if="error">Oh, snap! The berries are all gone!</div>
<Suspense v-else-if="selectedBerry">
<template #default>
<BerryDetails :url="selectedBerry" :key="selectedBerry" />
</template>
<template #fallback> Fetching berry details... </template>
</Suspense>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onErrorCaptured, watch } from "vue";
import useBerries from "../hooks/useBerries";
import BerryDetails from "./BerryDetails.vue";
export default defineComponent({
name: "Berries",
async setup() {
const selectedBerry = ref("");
// Added error ref
const error = ref();
// Added onErrorCaptured lifecycle hook
onErrorCaptured((e) => {
error.value = e;
return true;
});
// Reset error when selectedBerry is updated
watch(selectedBerry, () => (error.value = null));
const berries = await useBerries();
return { berries, selectedBerry, error };
},
components: {
BerryDetails,
},
});
</script>
我们在这个组件中做了三件事:
- 我们添加了一个名为 的变量
error
,它是一个 ref。 - 我们添加了生命周期钩子
onErrorCaptured
,只要从子组件中捕获错误就会触发。在这种情况下,如果 API 请求失败,我们将捕获错误,并相应地更新我们的显示。 - 我们添加了一个 watch 方法,
selectedBerry
用于在选择新浆果时重置错误。
或者,这可以通过 try/catch in 来处理BerryDetails.vue
。在这种情况下,子组件需要处理错误,而不是让它冒泡到暂停渲染的父组件。
结论
Suspense 提供了一种内置方法来处理涉及从 API 获取数据或执行一些其他异步操作的用例。Vue 3 无需编写自定义逻辑,而是为开发人员提供了构建用户友好的应用程序和体验的工具。获取或加载数据是单页应用程序的常见任务,重要的是通知用户幕后正在发生某些事情。下次您发现自己在获取数据时,请考虑 Suspense API 是否适合您正在构建的界面。