Suspense
Экспериментальная возможность
<Suspense>
является экспериментальной функцией. Не гарантируется, что она достигнет стабильного состояния, и API может измениться до того, как это произойдёт.
<Suspense>
- это встроенный компонент для организации асинхронных зависимостей в дереве компонентов. Он может отображать состояние загрузки в ожидании разрешения нескольких вложенных асинхронных зависимостей в дереве компонентов.
Асинхронные зависимости
Чтобы пояснить, какую проблему пытается решить <Suspense>
и как она взаимодействует с этими асинхронными зависимостями, представим себе иерархию компонентов следующим образом:
<Suspense>
└─ <Dashboard>
├─ <Profile>
│ └─ <FriendStatus> (компонент с async setup())
└─ <Content>
├─ <ActivityFeed> (асинхронный компонент)
└─ <Stats> (асинхронный компонент)
В дереве компонентов есть несколько вложенных компонентов, рендеринг которых зависит от того, какой асинхронный ресурс должен быть разрешен первым. Без <Suspense>
каждый из них должен будет обрабатывать свои собственные состояния загрузки/ошибки и загрузки. В худшем случае мы можем увидеть на странице три загрузочных спиннера, содержимое которых будет отображаться в разное время.
Компонент <Suspense>
дает нам возможность отображать состояния загрузки/ошибки верхнего уровня, пока мы ожидаем разрешения этих вложенных асинхронных зависимостей.
Существует два типа асинхронных зависимостей, которые может ждать <Suspense>
:
Компоненты с асинхронным хуком
setup()
. Сюда относятся компоненты, использующие<script setup>
с выражениямиawait
верхнего уровня.
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>
, то он будет рассматриваться как асинхронная зависимость этого <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
- Поддерживается только с версии 3.3+
Когда у нас есть несколько асинхронных компонентов (обычно при использовании вложенных маршрутов или маршрутов на основе лейаутов), как например:
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>
служит только как еще одна "граница" для разрешения зависимостей и исправления.
Связанное