在處理 API 或其他非同步事件時,常常需要另外定義其他狀態:例如判斷是否已拿到資料等。
在 Vue3 中 我們可以使用 Suspense
輕鬆完成這件事情。
首先新增 <Suspense>
,並新增 #default 和 #fallback 兩個插槽,兩者分別代表:
#default
:顯示「非同步處理完後」的畫面。#fallback
:顯示「非同步處理完前」的畫面。簡易的程式碼如下:
<script setup>
import Dashboard from "./components/Dashboard.vue";
</script>
<template>
<Suspense>
<template #default>
<Dashboard :time="2000" />
</template>
<template #fallback>
Loading...
</template>
</Suspense>
</template>
Dashboard 組件的程式碼如下,我們建立了一個 Promise,中間再包一個計時器,來模擬非同步行為,計時器的秒數從父層中傳遞進來:
<script setup>
const { time } = defineProps({
time: { type: Number },
});
const promise = (time) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, time);
});
};
await promise(time);
</script>
<template>
<img src="https://seeklogo.com/images/V/vite-logo-BFD4283991-seeklogo.com.png" />
</template>
上面的範例中,2 秒後 Promise
會 resolve()
,接著會看到一張圖:
(補充:這邊因為我有用 transition,所以有漸變的動畫)
如果在組件中使用的是 script setup
,那 script 中就會變成非同步行為,即在外層加了一個 async,因此我們可以在內部使用 await
。
反之如果是另一種寫法,外層要加上 async:
export default {
async setup() {
const res = await fetch(...)
const posts = await res.json()
return {
posts
}
}
}
這個非同步行為會返回 Promise,接著如果被 resolve() 後則顯示 #default
的內容。
如果過程中發生錯誤,我們可以使用生命週期的 onErrorCaptured
來捕捉,這個生命週期與 Suspense 兩者是獨立的。
<script setup>
import Dashboard from "./components/Dashboard.vue";
import { onErrorCaptured } from "vue";
onErrorCaptured((err) => {
console.log(err)
})
</script>
我們可能會有 #default
中放入多個組件的需求:
<template>
<Transition>
<Suspense>
<template #default>
<div>
<Dashboard :time="2000" />
<Dashboard :time="5000" />
</div>
</template>
<template #fallback> Loading... </template>
</Suspense>
</Transition>
</template>
這邊會等到所有組件都完成後才會顯示,以上面這個例子是 5 秒。
PS. 如果有多個組件記得加一個根標籤在外部,否則會收到 [Vue warn]: <Suspense> slots expect a single root node.
的錯誤訊息。
如果有需求的話,也可以配合 <KeepAlive>
、<Transition>
等其他組件一起使用。
Suspense
幫助我們更方便的管理非同步中的狀態,不過這個功能目前還在實驗階段,對這方面有疑慮的話,可能要考慮是否用在自己的專案上。