前言
Suspense是Vue 3新增的內(nèi)置標(biāo)簽,盡管目前官方文檔里并沒(méi)有Suspense的介紹安皱,但不妨礙我們先學(xué)習(xí)它迄委。
每當(dāng)我們希望組件等待數(shù)據(jù)獲取時(shí)(通常在異步API調(diào)用中),我們都可以使用Vue3 Composition API制作異步組件信峻。
以下是異步組件有用的一些實(shí)例:
- 在頁(yè)面加載之前顯示加載動(dòng)畫(huà)
- 顯示占位符內(nèi)容
- 處理延遲加載的圖像
以前,在Vue 2中祭饭,我們必須使用條件(例如 v-if 或 v-else)來(lái)檢查我們的數(shù)據(jù)是否已加載并顯示后備內(nèi)容芜茵。
但是現(xiàn)在,Suspense隨Vue3內(nèi)置了倡蝙,因此我們不必?fù)?dān)心跟蹤何時(shí)加載數(shù)據(jù)并呈現(xiàn)相應(yīng)的內(nèi)容九串。
父組件
我們通過(guò)范例學(xué)習(xí)Suspense,首先編寫(xiě)一個(gè)父組件悠咱。
- <template>
<template>里使用了<Suspense>標(biāo)簽蒸辆,即便你完全不懂<Suspense>的用法,看了范例也該看明白析既,default插槽里要放正式內(nèi)容,fallback插槽里要放降級(jí)內(nèi)容谆奥,我放了一行字Loading ...
眼坏,你也可以放菊花圖之類(lèi)的東西。
- <script setup>
變量名asyncCom必須與模板里的組件名一致酸些。defineAsyncComponent方法用來(lái)動(dòng)態(tài)引入組件宰译。
<template>
<div>
子組件內(nèi)容:
<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>沒(méi)什么可說(shuō)的。
<script setup>默認(rèn)就是異步的魄懂,相當(dāng)于async setup(){}沿侈,所以你可以放心在里面寫(xiě)await。fetchData是我寫(xiě)的模擬ajax請(qǐng)求的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>
效果
頁(yè)面會(huì)顯示1.5秒的Loading...缀拭,然后顯示一個(gè)列表。
原理
Vue從上到下執(zhí)行子組件的setup里的全部語(yǔ)句填帽,執(zhí)行完同步語(yǔ)句(包括await語(yǔ)句)之后蛛淋,父組件就認(rèn)為子組件加載完成,在這之前篡腌,子組件setup狀態(tài)始終未pending褐荷,所以父組件顯示降級(jí)內(nèi)容(Loading...),等子組件setup的狀態(tài)變成resolved或者rejected嘹悼,父組件就顯示默認(rèn)內(nèi)容叛甫。
捕獲異常
先說(shuō)怎么顯示異常:我的計(jì)劃是,如果出現(xiàn)異常杨伙,就不再顯示Loading...其监,而是顯示“Loading Error. Retry?”,其中Retry做成一個(gè)按鈕缀台。
現(xiàn)在ref: jsonData = await fetchData();
這句根本沒(méi)有考慮異常情況棠赛,那么怎么捕獲異常呢?
父組件
我們定義一個(gè)布爾值asyncComShow負(fù)責(zé)刷新組件,同時(shí)給<async-com>綁上事件@retry="retry"睛约。retry函數(shù)要做的事情就是隱藏組件然后再顯示鼎俘,借此刷新組件。
<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>
子組件
首先編寫(xiě)錯(cuò)誤提示辩涝,然后定義變量errorShow來(lái)切換提示贸伐。
const instance = getCurrentInstance();用于提供emit方法向父組件發(fā)出retry申請(qǐng)。也可以在頂層定義const context = useContext();怔揩,然后用context.emit("retry");捉邢,我建議用后者,因?yàn)闃?biāo)準(zhǔn)且輕量級(jí)商膊。
之后伏伐,我們用try...catch...來(lái)捕獲錯(cuò)誤。
最后晕拆,await getList()的await是必須要寫(xiě)的藐翎,不然setup生命期會(huì)在瞬間結(jié)束,Loading提示也只會(huì)顯示一瞬間实幕。
<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>