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
. Оба слота допускают только один ближайший дочерний узел. По возможности показывается узел в слоте по умолчанию (#default
). Если нет, то вместо него будет показан узел в резервном слоте (#fallback
).
template
<Suspense>
<!-- компонент с вложенными асинхронными зависимостями -->
<Dashboard />
<!-- загрузка состояния через слот #fallback -->
<template #fallback>
Загрузка...
</template>
</Suspense>
При первом рендеринге <Suspense>
отобразит содержимое своего слота по умолчанию в памяти. Если во время выполнения процесса возникнут какие-либо асинхронные зависимости, он перейдет в состояние pending. В состоянии ожидания будет отображаться содержимое резервного слота. Когда все встретившиеся асинхронные зависимости разрешены, <Suspense>
переходит в состояние resolved и отображается разрешенное содержимое слота по умолчанию.
Если во время первоначального рендеринга не было обнаружено никаких асинхронных зависимостей, <Suspense>
сразу перейдет в состояние resolved.
Находясь в состоянии разрешения, <Suspense>
вернется в состояние ожидания, только если корневой узел слота #default
будет заменен. Новые асинхронные зависимости, вложенные глубже в дерево, не приведут к возврату <Suspense>
в состояние ожидания.
Когда произойдет возврат, содержимое резервного слота не будет отображаться сразу. Вместо этого <Suspense>
будет отображать предыдущее содержимое #default
, ожидая разрешения нового содержимого и его асинхронных зависимостей. Это поведение можно настроить с помощью параметра timeout
: <Suspense>
будет переключаться на резервное содержимое, если для отрисовки нового содержимого по умолчанию потребуется больше времени, чем timeout
. Значение timeout
, равное 0
, приведет к тому, что содержимое резервного слота будет отображаться сразу после замены содержимого по умолчанию.
События
Компонент <Suspense>
испускает 3 события: pending
, resolve
и fallback
. Событие pending
возникает при переходе в состояние ожидания. Событие resolve
происходит, когда новое содержимое закончило разрешаться в слоте default
. Событие 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
отображает пустой узел до тех пор, пока он не будет разрешён (вместо предыдущего или резервного слота).
Чтобы решить эту проблему, мы могли бы иметь вложенный Suspense для обработки патча для вложенного компонента, например:
template
<Suspense>
<component :is="DynamicAsyncOuter">
<Suspense suspensible> <!-- this -->
<component :is="DynamicAsyncInner" />
</Suspense>
</component>
</Suspense>
Если вы не зададите параметр suspensible
, внутренний <Suspense>
будет восприниматься родительским <Suspense>
как синхронный компонент. Это означает, что у него есть свой собственный резервный слот, если оба компонента Dynamic
изменяются одновременно, могут быть пустые узлы и несколько циклов исправления, пока дочерний <Suspense>
загружает свое собственное дерево зависимостей, что может оказаться нежелательным. Когда он установлен, вся обработка асинхронных зависимостей передается родительскому <Suspense>
(включая испускаемые события), а внутренний <Suspense>
служит исключительно как ещё одна граница для разрешения зависимостей и исправления.
Ссылки по теме