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> обычным способом.

Вложенные Suspense

Когда у нас есть несколько асинхронных компонентов (обычно для вложенных или основанных на лаяуте маршрутов), как например:

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

<Suspense> создает "границу", которая будет регулировать все асинхронные компоненты вниз по дереву, как и ожидается. Однако, когда мы изменяем DynamicAsyncOuter, <Suspense> это корректно обрабатывает, но когда мы изменяем DynamicAsyncInner, вложенный DynamicAsyncInner отображает пустой node-узел, пока он не будет разрешен (вместо предыдущего или fallback-слота).

Чтобы решить эту проблему, мы могли бы иметь вложенный <Suspense> для обработки патча для вложенного компонента, например:

template
<Suspense>
  <component :is="DynamicAsyncOuter">
    <Suspense suspensible> <!-- этот -->
      <component :is="DynamicAsyncInner" />
    </Suspense>
  </component>
</Suspense>

Если вы не установите параметр suspensible, внутренний <Suspense> будет рассматриваться родительским <Suspense> как синхронным компонентом. Это означает, что у него есть свой собственный fallback-слот, и если оба компонента Dynamic изменятся одновременно, могут возникнуть пустые node-узлы и несколько циклов исправления, пока дочерний <Suspense> будет загружать свое собственное дерево зависимостей, что может быть нежелательно. Когда этот параметр установлен, вся асинхронная обработка зависимостей передается родительскому <Suspense> (включая пользовательские события), а внутренний <Suspense> служит только как еще одна "граница" для разрешения зависимостей и исправления.


Связанное

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