跳轉到內容

Suspense

實驗性功能

<Suspense>是一個實驗性功能。它可能無法達到穩定狀態,並且在其達到穩定狀態之前,API可能會發生變化。

<Suspense>是一個內建元件,用於在元件樹中協調非同步依賴。在等待多個巢狀非同步依賴在元件樹中解決時,它可以渲染載入狀態。

非同步依賴

為了解釋<Suspense>試圖解決的問題以及它與這些非同步依賴的互動方式,讓我們想象一個如下所示的元件層次結構

<Suspense>
└─ <Dashboard>
   ├─ <Profile>
   │  └─ <FriendStatus> (component with async setup())
   └─ <Content>
      ├─ <ActivityFeed> (async component)
      └─ <Stats> (async component)

在元件樹中,有多個巢狀元件,它們的渲染依賴於一些需要首先解決的非同步資源。在沒有<Suspense>的情況下,每個元件都需要處理自己的載入/錯誤和載入完成狀態。在最壞的情況下,我們可能在頁面上看到三個載入指示器,內容在不同時間顯示。

<Suspense>元件為我們提供了在等待這些巢狀非同步依賴解決時顯示頂層載入/錯誤狀態的能力。

<Suspense>可以等待兩種型別的非同步依賴

  1. 具有非同步setup()鉤子的元件。這包括使用帶有頂級await表示式的<script setup>的元件。

  2. 非同步元件.

async setup()

Composition API 元件的 setup() 鉤子可以是非同步的

js
export default {
  async setup() {
    const res = await fetch(...)
    const posts = await res.json()
    return {
      posts
    }
  }
}

如果使用 <script setup>,頂級 await 表示式的存在會自動使元件成為非同步依賴

vue
<script setup>
const res = await fetch(...)
const posts = await res.json()
</script>

<template>
  {{ posts }}
</template>

非同步元件

非同步元件預設是 "可掛起的"。這意味著如果它在父級鏈中有一個 <Suspense>,它將被視為該 <Suspense> 的非同步依賴。在這種情況下,載入狀態將由 <Suspense> 控制,並且將忽略元件自己的載入、錯誤、延遲和超時選項。

非同步元件可以透過在其選項中指定 suspensible: false 來選擇退出 Suspense 控制,並讓元件始終控制其自己的載入狀態。

載入狀態

<Suspense> 元件有兩個插槽:#default#fallback。這兩個插槽都只允許有一個直接子節點。如果可能,將顯示預設插槽中的節點。如果不可以,將顯示回退插槽中的節點。

template
<Suspense>
  <!-- component with nested async dependencies -->
  <Dashboard />

  <!-- loading state via #fallback slot -->
  <template #fallback>
    Loading...
  </template>
</Suspense>

在初次渲染時,<Suspense> 將在記憶體中渲染其預設插槽內容。如果在處理過程中遇到任何非同步依賴,它將進入一個 掛起 狀態。在掛起狀態下,將顯示回退內容。當所有遇到的非同步依賴都已解決,<Suspense> 進入一個 已解決 狀態,並顯示已解決的預設插槽內容。

如果在初次渲染過程中沒有遇到任何非同步依賴,<Suspense> 將直接進入已解決狀態。

一旦進入已解決狀態,<Suspense> 只有在 #default 插槽的根節點被替換時才會回到掛起狀態。巢狀在樹中的新非同步依賴 不會 導致 <Suspense> 回到掛起狀態。

當發生回退時,回退內容不會立即顯示。相反,<Suspense> 將在等待新內容及其非同步依賴解決的同時顯示之前的 #default 內容。此行為可以透過 timeout 屬性進行配置:<Suspense> 將在 timeout 指定的時間後切換到回退內容。如果 timeout 的值為 0,則當預設內容被替換時,將立即顯示回退內容。

事件

<Suspense> 元件發出 3 個事件:pendingresolvefallback。當進入掛起狀態時,將觸發 pending 事件。當新內容在 default 插槽中完成解決時,將發出 resolve 事件。當顯示 fallback 插槽的內容時,將觸發 fallback 事件。

可以使用這些事件,例如,在載入新元件時在舊 DOM 前顯示載入指示器。

錯誤處理

<Suspense> 目前不透過元件本身提供錯誤處理 - 然而,您可以使用 errorCaptured 選項或 onErrorCaptured() 鉤子來捕獲和處理 <Suspense> 父元件中的非同步錯誤。

與其他元件結合

在組合使用<Transition><KeepAlive>元件時,通常需要使用<Suspense>。這些元件的巢狀順序對於它們正常工作至關重要。

此外,這些元件通常與Vue Router中的<RouterView>元件一起使用。

以下示例展示瞭如何巢狀這些元件,以便它們都按預期工作。對於更簡單的組合,您可以刪除不需要的元件。

template
<RouterView v-slot="{ Component }">
  <template v-if="Component">
    <Transition mode="out-in">
      <KeepAlive>
        <Suspense>
          <!-- main content -->
          <component :is="Component"></component>

          <!-- loading state -->
          <template #fallback>
            Loading...
          </template>
        </Suspense>
      </KeepAlive>
    </Transition>
  </template>
</RouterView>

Vue Router內建了對使用動態匯入進行懶載入元件的支援。這些與非同步元件不同,目前它們不會觸發<Suspense>。然而,它們可以作為非同步元件的子元件,並且可以透過常規方式觸發<Suspense>

巢狀Suspense

  • 僅在3.3及以上版本支援

當我們有多個非同步元件(常見於巢狀或基於佈局的路由)時,如下所示:

template
<Suspense>
  <component :is="DynamicAsyncOuter">
    <component :is="DynamicAsyncInner" />
  </component>
</Suspense>

<Suspense>建立了一個邊界,將解析樹中的所有非同步元件,正如預期的那樣。然而,當我們更改DynamicAsyncOuter時,<Suspense>正確等待它,但當更改DynamicAsyncInner時,巢狀的DynamicAsyncInner將渲染一個空節點,直到它被解析(而不是之前的節點或回退插槽)。

為了解決這個問題,我們可以使用巢狀的<Suspense>來處理巢狀元件的補丁,如下所示:

template
<Suspense>
  <component :is="DynamicAsyncOuter">
    <Suspense suspensible> <!-- this -->
      <component :is="DynamicAsyncInner" />
    </Suspense>
  </component>
</Suspense>

如果您沒有設定suspensible屬性,父級<Suspense>會將內部<Suspense>視為同步元件。這意味著它有自己的回退插槽,如果兩個Dynamic元件同時更改,在子<Suspense>載入其自己的依賴項樹時,可能會有空節點和多個補丁週期,這可能不是所希望的。當設定了該屬性後,所有非同步依賴項處理都由父級<Suspense>處理(包括髮出的事件),而內部<Suspense>僅作為依賴項解析和補丁的另一個邊界。


相關

Suspense已載入