Skip to content

Suspense

Экспериментальная возможность

<Suspense> является экспериментальной функцией. Не гарантируется, что она достигнет стабильного состояния, и API может измениться до того, как это произойдёт.

<Suspense> - это встроенный компонент для организации асинхронных зависимостей в дереве компонентов. Он может отображать состояние загрузки в ожидании разрешения нескольких вложенных асинхронных зависимостей в дереве компонентов.

Асинхронные зависимости

Чтобы пояснить, какую проблему пытается решить <Suspense> и как она взаимодействует с этими асинхронными зависимостями, представим себе иерархию компонентов следующим образом:

<Suspense>
└─ <Dashboard>
   ├─ <Profile>
   │  └─ <FriendStatus> (компонент с async setup())
   └─ <Content>
      ├─ <ActivityFeed> (асинхронный компонент)
      └─ <Stats> (асинхронный компонент)

В дереве компонентов есть несколько вложенных компонентов, рендеринг которых зависит от того, какой асинхронный ресурс должен быть разрешен первым. Без <Suspense> каждый из них должен будет обрабатывать свои собственные состояния загрузки/ошибки и загрузки. В худшем случае мы можем увидеть на странице три загрузочных спиннера, содержимое которых будет отображаться в разное время.

Компонент <Suspense> дает нам возможность отображать состояния загрузки/ошибки верхнего уровня, пока мы ожидаем разрешения этих вложенных асинхронных зависимостей.

Существует два типа асинхронных зависимостей, от которых может ждать <Suspense>:

  1. Компоненты с асинхронным хуком setup(). Сюда относятся компоненты, использующие <script setup> с выражениями await верхнего уровня.

  2. Асинхронные компоненты.

async setup()

Хук setup() компонента Composition API может быть асинхронным:

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>, то он будет рассматриваться как async-зависимость от этого <Suspense>. В этом случае состояние загрузки будет контролироваться <Suspense>, а собственные параметры загрузки, ошибки, задержки и таймаута компонента будут игнорироваться.

Асинхронные компоненты могут отказаться от управления Suspense и позволить компоненту всегда контролировать собственное состояние загрузки, указав в параметрах suspensible: false.

Состояние загрузки

Компонент <Suspense> имеет два слота: #default и #fallback. Оба слота допускают только один непосредственный дочерний узел. Узел в слоте по умолчанию показывается, если это возможно. Если это невозможно, то вместо него будет показан узел в слоте fallback.

template
<Suspense>
  <!-- компонент с вложенными асинхронными зависимостями -->
  <Dashboard />

  <!-- загрузка состояния через #fallback слот -->
  <template #fallback>
    Загрузка...
  </template>
</Suspense>

При первом рендеринге <Suspense> выводит содержимое слота по умолчанию в память. Если во время этого процесса будут обнаружены какие-либо асинхронные зависимости, то он перейдет в состояние ожидания. Во время ожидания будет отображаться содержимое резервного слота. Когда все асинхронные зависимости будут разрешены, <Suspense> перейдет в состояние разрешения и будет отображено содержимое разрешенного слота по умолчанию.

Если при начальном рендеринге не было обнаружено асинхронных зависимостей, то <Suspense> сразу перейдет в состояние resolved.

Находясь в разрешенном состоянии, <Suspense> вернется в состояние ожидания только в случае замены корневого узла слота #default. Новые асинхронные зависимости, вложенные глубже в дерево, не приведут к переходу <Suspense> в состояние ожидания.

Когда происходит возврат, содержимое резервной копии не будет отображаться немедленно. Вместо этого <Suspense> будет отображать предыдущее содержимое #default, ожидая разрешения нового содержимого и его асинхронных зависимостей. Такое поведение может быть настроено с помощью параметра timeout: <Suspense> будет переключаться на резервное содержимое, если для отображения нового содержимого по умолчанию требуется больше времени, чем timeout. При значении timeout, равном 0, резервное содержимое будет отображаться сразу после замены стандартного содержимого.

События

Компонент <Suspense> генерирует 3 события: pending, resolve и fallback. Событие pending возникает при переходе в состояние ожидания. Событие resolve возникает при завершении разрешения нового содержимого в слоте по умолчанию. Событие fallback происходит, когда отображается содержимое слота fallback.

Эти события могут быть использованы, например, для отображения индикатора загрузки перед старым DOM во время загрузки новых компонентов.

Обработка ошибок

В настоящее время <Suspense> не предоставляет возможности обработки ошибок через сам компонент, однако вы можете использовать опцию errorCaptured или хук onErrorCaptured() для перехвата и обработки ассинхронных ошибок в родительском компоненте <Suspense>.

Комбинирование с другими компонентами

Часто требуется использовать <Suspense> в сочетании с компонентами <Transition> и <KeepAlive>. Порядок вложения этих компонентов важен для обеспечения их корректной работы.

Кроме того, эти компоненты часто используются совместно с компонентом <RouterView> из Vue Router.

В следующем примере показано, как вложить эти компоненты так, чтобы все они работали как положено. Для получения более простых комбинаций можно удалить ненужные компоненты:

template
<RouterView v-slot="{ Component }">
  <template v-if="Component">
    <Transition mode="out-in">
      <KeepAlive>
        <Suspense>
          <!-- основное содержание -->
          <component :is="Component"></component>

          <!-- состояние загрузки -->
          <template #fallback>
            Загрузка...
          </template>
        </Suspense>
      </KeepAlive>
    </Transition>
  </template>
</RouterView>

Во Vue Router встроена поддержка ленивой загрузки компонентов с помощью динамического импорта. Они отличаются от асинхронных компонентов и в настоящее время не вызывают <Suspense>. Однако они могут иметь в качестве потомков асинхронные компоненты, которые могут вызывать <Suspense> обычным способом.

Nested Suspense

When we have multiple async components (common for nested or layout-based routes) like this:

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

<Suspense> creates a boundary that will resolve all the async components down the tree, as expected. However, when we change DynamicAsyncOuter, <Suspense> awaits it correctly, but when we change DynamicAsyncInner, the nested DynamicAsyncInner renders an empty node until it has been resolved (instead of the previous one or fallback slot).

In order to solve that, we could have a nested suspense to handle the patch for the nested component, like:

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

If you don't set the suspensible prop, the inner <Suspense> will be treated like a sync component by the parent <Suspense>. That means that it has its own fallback slot and if both Dynamic components change at the same time, there might be empty nodes and multiple patching cycles while the child <Suspense> is loading its own dependency tree, which might not be desirable. When it's set, all the async dependency handling is given to the parent <Suspense> (including the events emitted) and the inner <Suspense> serves solely as another boundary for the dependency resolution and patching.


Связанное

SuspenseУже загружено