微信搜尋 【大遷世界】, 我會第一時間和你分享前端行業趨勢,學習途徑等等。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。
Suspense 不是你想的那樣。是的,它幫助我們處理非同步元件,但它的作用遠不止於此。
Suspense 允許我們協調整個應用程式的載入狀態,包括所有深度巢狀的元件。而不是像一個爆米花使用者介面一樣,到處都是 loading,元件突然奔的一下到位
有了 Suspense, 我們可以有一個單一的、有組織的系統,一次性載入所有東西。
而且,Suspense 也給我們提供了細粒度的控制,所以如果需要的話,我們可以在兩者之間實現一些東西。
在這篇文章中,我們將學到很多關於 Suspense 的知識--它是什麼,能幹什麼,以及如何使用它。
首先,我們將仔細看看這些爆米花介面。然後,在看看如何使用Suspense來解決這些問題。之後,嘗試透過在整個應用程式中巢狀Suspense來獲得更精細的控制。最後,簡單看看如何使用佔位符來豐富我們的使用者介面。
爆米花UI-- Suspense 之前
事例地址:https://codesandbox.io/s/unco...
如果沒有 Suspense,每個元件都必須單獨處理其載入狀態。
這可能導致一些相當糟糕的使用者體驗,多個載入按鈕和內容突然出現在螢幕上,就像你在製作爆米花一樣。
雖然,我們可以建立抽象元件來處理這些載入狀態,但這比使用Suspense要困難得多。有一個單一的點來管理載入狀態,比每個元件做自己的事情要容易維護得多。
在事例中,我們使用BeforeSuspense
元件來模擬一個內部處理載入狀態的元件。把它命名為BeforeSuspense
,因為一旦我們實現了Suspense,我們就會把它重構為WithSuspense
元件。
BeforeSuspense.vue
<template>
<div class="async-component" :class="!loading && 'loaded'">
<Spinner v-if="loading" />
<slot />
</div>
</template>
<script setup>
import { ref } from 'vue'
import Spinner from './Spinner.vue'
const loading = ref(true)
const { time } = defineProps({
time: {
type: Number,
default: 2000
}
})
setTimeout(() => (loading.value = false), time)
</script>
一開始設定 loading 為 true
,所以顯示 Spinner
元件。然後,當setTimeout
完成後,將 loading 設定為 false
,隱藏 Spinner
並使元件的背景為綠色。
在這個元件中,還包括一個 slot
,這樣其它元件就可以套在 BeforeSuspense
元件中了:
<template>
<button @click="reload">Reload page</button>
<BeforeSuspense time="3000">
<BeforeSuspense time="2000" />
<BeforeSuspense time="1000">
<BeforeSuspense time="500" />
<BeforeSuspense time="4000" />
</BeforeSuspense>
</BeforeSuspense>
</template>
沒有什麼太花哨的東西。只是一些巢狀的元件,有不同的時間值傳遞給它們。
下面,我們來看看怎麼透過使用 Suspense
元件來改進這個爆米花使用者介面。
Suspense
事例地址:https://codesandbox.io/s/coor...
現在,我們使用Suspense
來處理這些亂七八糟的東西,並將其變成一個更好的使用者體驗。
不過,首先我們需要快速瞭解一下Suspense到底是什麼
Suspense 基礎知識
以下是 Suspense 部分的基本結構
<Suspense>
<!-- Async component here -->
<template #fallback>
<!-- Sync loading state component here -->
</template>
</Suspense>
為了使用Suspense,把非同步元件放入 default
槽,把回退載入狀態放入 fallback
槽。
非同步元件是以下兩種情況之一:
- 一個帶有
async setup
函式的元件,該元件返回一個Promise,或者在script setup
中使用頂級await
- 使用
defineAsyncComponent
非同步載入的元件
無論哪種方式,我們最終都會得到一個開始未解決 的 Promise ,然後最終得到解決。
當該 Promise 未被解決時,Suspense 元件將顯示 fallback
槽中的內容。然後,當Promise 被解決時,它會在 default
槽中顯示該非同步元件。
注意: 這裡沒有錯誤處理路基。起初我以為有,但這是對懸念的一個常見誤解。如果想知道是什麼導致了錯誤。可以使用onErrorCaptured
鉤子來捕捉錯誤,但這是一個獨立於Suspense的功能。
現在我們對Suspense有了一些瞭解,讓我們回到我們的演示應用程式。
管理非同步依賴關係
為了讓Suspense
管理我們的載入狀態,首先需要將BeforeSuspense
元件轉換成一個非同步元件
我們將它命名為 WithSuspense,內容如下:
<template>
<div class="async-component loaded">
<!-- 這裡不需要一個 Spiner 了,因為載入是在根部處理的 -->
<slot />
</div>
</template>
<script setup>
const { time } = defineProps({
time: {
type: Number,
required: true
}
})
// 加入一個延遲,以模擬載入資料
await new Promise(resolve => {
setTimeout(() => {
resolve()
}, time)
})
</script>
我們已經完全刪除了載入狀態的Spinner,因為這個元件不再有載入狀態了。
因為這是一個非同步元件,setup
函式直到它完成載入才會返回。該元件只有在 setup 函式完成後才會被載入。因此,與BeforeSuspense
元件不同,WithSuspense
元件內容在載入完畢之前不會被渲染。
這對任何非同步元件來說都是如此,不管它是如何被使用的。在setup函式返回(如果是同步的)或解析(如果是非同步的)之前,它不會渲染任何東西。
有了WithSuspense元件,我們仍然需要重構我們的App
元件,以便在Suspens
e元件中使用這個元件。
<template>
<button @click="reload">Reload page</button>
<Suspense>
<WithSuspense :time="2000">
<WithSuspense :time="1500" />
<WithSuspense :time="1200">
<WithSuspense :time="1000" />
<WithSuspense :time="5000" />
</WithSuspense>
</WithSuspense>
<template #fallback>
<Spinner />
</template>
</Suspense>
</template>
結構和之前一樣,但這次是在 Suspense 元件的預設槽中。我們還加入了 fallback 槽,在載入時渲染我們的Spinner元件。
在演示中,你會看到它顯示載入按鈕,直到所有的元件都載入完畢。只有在那時,它才會顯示現在完全載入的元件樹。
非同步瀑布
如果你仔細注意,你會注意到這些元件並不像你想象的那樣是並聯載入的。
總的載入時間不是基於最慢的元件(5秒)。相反,這個時間要長得多。這是因為Vue只有在父非同步元件完全解析後才會開始載入子元件。
你可以透過把日誌放到WithSuspense組
件中來測試這一點。一個在安裝開始跟蹤安裝,一個在我們呼叫解決之前。
最初使用BeforeSuspense
元件的例子中,整個元件樹被掛載,無需等待,所有的 "非同步 "操作都是並行啟動的。這意味著Suspense
有可能透過引入這種非同步瀑布而影響效能。所以請記住這一點。
巢狀 Suspense 以隔離子樹
事例地址:https://codesandbox.io/s/nest...
這裡有一個深度巢狀的元件,它需要整整5秒來載入,阻塞了整個UI,儘管大多陣列件載入完成的時間要早得多。
但對我們來說有一個解決方案?
透過進一步巢狀第二個Suspense元件,我們可以在等待這個元件完成載入時顯示應用程式的其他部分。
<template>
<button @click="reload">Reload page</button>
<Suspense>
<WithSuspense :time="2000">
<WithSuspense :time="1500" />
<WithSuspense :time="1200">
<WithSuspense :time="1000" />
<!-- Nest a second Suspense component -->
<Suspense>
<WithSuspense :time="5000" />
<template #fallback>
<Spinner />
</template>
</Suspense>
</WithSuspense>
</WithSuspense>
<template #fallback>
<Spinner />
</template>
</Suspense>
</template>
將其包裹在第二個Suspense
元件中,使其與應用程式的其他部分隔離。Suspense元件本身是一個同步元件,所以當它的父級元件被載入時,它就會被載入。
然後它將顯示它自己的 fallback 內容,直到5秒結束。
透過這樣做,我們可以隔離應用程式中載入較慢的部分,減少我們的首次互動時間。在某些情況下,這可能是必要的,特別是當你需要避免非同步瀑布時。
從功能的角度來看,這也是有意義的。你的應用程式的每個功能或 "部分"都可以被包裹在它自己的Suspense
元件中,所以每個功能的載入都是一個單一的邏輯單元。
當然,如果你用 "Suspense" 包裝每一個組成部分,我們就會回到我們開始的地方。我們可以選擇以任何最合理的方式來批處理我們的載入狀態。
使用佔位符的 Suspense
事例地址: https://codesandbox.io/s/plac...
與其使用單一的 spinner,佔位符元件往往可以提供更好的體驗。
這種方式向使用者展示將要展示的內容,並讓他們在介面渲染前有一種期待的感覺。這是 spinner 無法做到的。
可以說--它們很時髦,看起來很酷。因此,我們重構程式碼,使用佔位符方式:
<template>
<button @click="reload">Reload page</button>
<Suspense>
<WithSuspense :time="2000">
<WithSuspense :time="1500" />
<WithSuspense :time="1200">
<WithSuspense :time="1000" />
<Suspense>
<WithSuspense :time="5000" />
<template #fallback>
<Placeholder />
</template>
</Suspense>
</WithSuspense>
</WithSuspense>
<template #fallback>
<!-- 這裡,複製實際資料的形狀 -->
<Placeholder>
<Placeholder />
<Placeholder>
<Placeholder />
<Placeholder />
</Placeholder>
</Placeholder>
</template>
</Suspense>
</template>
我們安排了這些Placeholder元件,並對它們進行了風格化處理,使它們看起來與WithSuspense
元件完全一樣。這提供了一個在載入和裝載狀態之間的無縫過渡。
在演示中,Placeholder
元件在背景上給我們提供了一個CSS動畫,以創造一個脈動的效果:
.fast-gradient {
background: linear-gradient(
to right,
rgba(255, 255, 255, 0.1),
rgba(255, 255, 255, 0.4)
);
background-size: 200% 200%;
animation: gradient 2s ease-in-out infinite;
}
@keyframes gradient {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
總結
爆米花的載入狀態是非常明顯的,會傷害使用者體驗。
幸運的是,Suspense 是一個很棒的新特性,它為我們在Vue應用程式中協調載入狀態提供了很多選擇。
然而,在寫這篇文章的時候,Suspense仍然被認為是實驗性的,所以要謹慎行事。關於它的狀態的最新資訊,請參考文件。
編輯中可能存在的bug沒法實時知道,事後為了解決這些bug,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
作者:Michael Thiessen 譯者:小智 來源:vueschool
原文:https://vueschool.io/articles...
交流
有夢想,有乾貨,微信搜尋 【大遷世界】 關注這個在凌晨還在刷碗的刷碗智。
本文 GitHub https://github.com/qq449245884/xiaozhi 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。