前言
Suspense是Vue 3新增的内置标签,尽管目前官方文档里并没有Suspense的介绍,但不妨碍我们先学习它。
每当我们希望组件等待数据获取时(通常在异步API调用中),我们都可以使用Vue3 Composition API制作异步组件。
以下是异步组件有用的一些实例:
- 在页面加载之前显示加载动画
- 显示占位符内容
- 处理延迟加载的图像
以前,在Vue 2中,我们必须使用条件(例如 v-if 或 v-else)来检查我们的数据是否已加载并显示后备内容。
但是现在,Suspense随Vue3内置了,因此我们不必担心跟踪何时加载数据并呈现相应的内容。
父组件
我们通过范例学习Suspense,首先编写一个父组件。
- <template>
<template>里使用了<Suspense>标签,即便你完全不懂<Suspense>的用法,看了范例也该看明白,default插槽里要放正式内容,fallback插槽里要放降级内容,我放了一行字Loading ...
,你也可以放菊花图之类的东西。
- <script setup>
变量名asyncCom必须与模板里的组件名一致。defineAsyncComponent方法用来动态引入组件。
<template>
<div>
子组件内容:
<Suspense>
<template #default>
<async-com />
</template>
<template #fallback>Loading ...</template>
</Suspense>
</div>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
const asyncCom = defineAsyncComponent(() => import("./asyncCom.vue"));
</script>
子组件(asyncCom.vue)
<template>没什么可说的。
<script setup>默认就是异步的,相当于async setup(){},所以你可以放心在里面写await。fetchData是我写的模拟ajax请求的Promise。
<template>
<div>
<ul>
<li v-for="item in jsonData" :key="item.name">
{{ item.name }} - {{ item.age }}
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from "vue";
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => {
resolve([
{
name: "张三",
age: 15,
},
{
name: "李四",
age: 17,
},
]);
}, 1500);
});
}
ref: jsonData = await fetchData();
</script>
效果
页面会显示1.5秒的Loading...,然后显示一个列表。
原理
Vue从上到下执行子组件的setup里的全部语句,执行完同步语句(包括await语句)之后,父组件就认为子组件加载完成,在这之前,子组件setup状态始终未pending,所以父组件显示降级内容(Loading...),等子组件setup的状态变成resolved或者rejected,父组件就显示默认内容。
捕获异常
先说怎么显示异常:我的计划是,如果出现异常,就不再显示Loading...,而是显示“Loading Error. Retry?”,其中Retry做成一个按钮。
现在ref: jsonData = await fetchData();
这句根本没有考虑异常情况,那么怎么捕获异常呢?
父组件
我们定义一个布尔值asyncComShow负责刷新组件,同时给<async-com>绑上事件@retry="retry"。retry函数要做的事情就是隐藏组件然后再显示,借此刷新组件。
<template>
<div>
<Suspense v-if="asyncComShow">
<template #default>
<async-com @retry="retry" />
</template>
<template #fallback> Loading ... </template>
</Suspense>
</div>
</template>
<script setup>
import { defineAsyncComponent, nextTick } from "vue";
const asyncCom = defineAsyncComponent(() => import("./asyncCom.vue"));
ref: asyncComShow = true;
function retry() {
asyncComShow = false;
nextTick(() => {
asyncComShow = true;
});
}
</script>
子组件
首先编写错误提示,然后定义变量errorShow来切换提示。
const instance = getCurrentInstance();用于提供emit方法向父组件发出retry申请。也可以在顶层定义const context = useContext();,然后用context.emit("retry");,我建议用后者,因为标准且轻量级。
之后,我们用try...catch...来捕获错误。
最后,await getList()的await是必须要写的,不然setup生命期会在瞬间结束,Loading提示也只会显示一瞬间。
<template>
<div>
<ul v-if="!errorShow">
<li v-for="item in jsonData" :key="item.name">
{{ item.name }} - {{ item.age }}
</li>
</ul>
<div v-else>
Loading Error. <button @click="retry">Retry?</button>
</div>
</div>
</template>
<script setup>
import { ref, nextTick, getCurrentInstance } from "vue";
const instance = getCurrentInstance();
ref: jsonData;
ref: errorShow = false;
function fetchData() {
return new Promise((resolve, reject) => {
setTimeout(() => {
reject([
{
name: "张三",
age: 15,
},
{
name: "李四",
age: 17,
},
]);
}, 1500);
});
}
function retry() {
instance.emit("retry");
}
async function getList() {
errorShow = false;
try {
jsonData = await fetchData();
} catch (e) {
errorShow = true;
}
}
await getList();
</script>