Suspense
Funkcja Eksperymentalna
<Suspense>
jest funkcją eksperymentalną. Nie ma gwarancji, że osiągnie status stabilny, a API może ulec zmianie zanim to nastąpi.
<Suspense>
jest wbudowanym komponentem służącym do orkiestracji asynchronicznych zależności w drzewie komponentów. Może wyrenderować stan ładowania podczas oczekiwania na rozwiązanie wielu zagnieżdżonych asynchronicznych zależności w drzewie komponentów.
Zależności Asynchroniczne
Aby wyjaśnić problem, który <Suspense>
próbuje rozwiązać i jak współdziała z tymi asynchronicznymi zależnościami, wyobraźmy sobie następującą hierarchię komponentów:
<Suspense> └─ <Dashboard> ├─ <Profile> │ └─ <FriendStatus> (komponent z asynchronicznym setup()) └─ <Content> ├─ <ActivityFeed> (asynchroniczny komponent) └─ <Stats> (asynchroniczny komponent)
W drzewie komponentów znajduje się wiele zagnieżdżonych komponentów, których renderowanie zależy od wcześniejszego rozwiązania zasobów asynchronicznych. Bez <Suspense>
, każdy z nich będzie musiał obsłużyć własne stany ładowania / błędu i załadowania. W najgorszym przypadku możemy zobaczyć trzy wskaźniki ładowania na stronie, z treścią wyświetlaną w różnych momentach.
Komponent <Suspense>
daje nam możliwość wyświetlania stanów ładowania / błędu na najwyższym poziomie, podczas gdy czekamy na rozwiązanie tych zagnieżdżonych zależności asynchronicznych.
Istnieją dwa typy zależności asynchronicznych, na które <Suspense>
może czekać:
Komponenty z asynchronicznym hookiem
setup()
. Dotyczy to komponentów używających<script setup>
z wyrażeniamiawait
na najwyższym poziomie.
async setup()
Hook setup()
komponentu Composition API może być asynchroniczny:
js
export default { async setup() { const res = await fetch(...) const posts = await res.json() return { posts } } }
Jeśli używasz <script setup>
, obecność wyrażeń await
na najwyższym poziomie automatycznie sprawia, że komponent staje się zależnością asynchroniczną:
vue
<script setup> const res = await fetch(...) const posts = await res.json() </script> <template> {{ posts }} </template>
Komponenty asynchroniczne
Komponenty asynchroniczne są domyślnie "suspensible" (obsługiwane przez Suspense). Oznacza to, że jeśli w łańcuchu komponentów nadrzędnych znajduje się <Suspense>
, będą one traktowane jako zależność asynchroniczna tego <Suspense>
. W tym przypadku stan ładowania będzie kontrolowany przez <Suspense>
, a własne opcje komponentu dotyczące ładowania, błędów, opóźnień i limitów czasu będą ignorowane.
Komponent asynchroniczny może zrezygnować z kontroli przez Suspense
i zawsze samodzielnie kontrolować swój stan ładowania, określając suspensible: false
w swoich opcjach.
Stan ładowania
Komponent <Suspense>
posiada dwa sloty: #default
oraz #fallback
. Oba sloty pozwalają tylko na jeden bezpośredni węzeł podrzędny. Węzeł w slocie domyślnym jest wyświetlany, jeśli to możliwe. Jeśli nie, zamiast niego zostanie wyświetlony węzeł ze slotu fallback.
template
<Suspense> <!-- komponent z zagnieżdżonymi zależnościami asynchronicznymi --> <Dashboard /> <!-- stan ładowania przez slot #fallback --> <template #fallback> Ładowanie... </template> </Suspense>
Podczas początkowego renderowania, <Suspense>
wyrenderuje zawartość domyślnego slotu w pamięci. Jeśli podczas tego procesu zostaną napotkane jakiekolwiek zależności asynchroniczne, przejdzie w stan oczekiwania. W stanie oczekiwania wyświetlana będzie zawartość fallback. Gdy wszystkie napotkane zależności asynchroniczne zostaną rozwiązane, <Suspense>
przejdzie w stan rozwiązany i zostanie wyświetlona rozwiązana zawartość domyślnego slotu.
Jeśli podczas początkowego renderowania nie napotkano żadnych zależności asynchronicznych, <Suspense>
przejdzie bezpośrednio w stan rozwiązany.
Po przejściu w stan rozwiązany, <Suspense>
powróci do stanu oczekiwania tylko wtedy, gdy główny węzeł slotu #default
zostanie zastąpiony. Nowe zależności asynchroniczne zagnieżdżone głębiej w drzewie nie spowodują powrotu <Suspense>
do stanu oczekiwania.
Gdy nastąpi powrót, zawartość fallback nie zostanie natychmiast wyświetlona. Zamiast tego, <Suspense>
wyświetli poprzednią zawartość #default
podczas oczekiwania na rozwiązanie nowej zawartości i jej zależności asynchronicznych. To zachowanie można skonfigurować za pomocą właściwości timeout
: <Suspense>
przełączy się na zawartość fallback, jeśli renderowanie nowej zawartości domyślnej zajmie dłużej niż timeout
. Wartość timeout
równa 0
spowoduje natychmiastowe wyświetlenie zawartości fallback po zastąpieniu zawartości domyślnej.
Zdarzenia
Komponent <Suspense>
emituje 3 zdarzenia: pending
, resolve
oraz fallback
. Zdarzenie pending
występuje podczas wejścia w stan oczekiwania. Zdarzenie resolve
jest emitowane, gdy nowa zawartość zostanie rozwiązana w slocie default
. Zdarzenie fallback
jest wyzwalane, gdy wyświetlana jest zawartość slotu fallback
.
Zdarzenia mogą być wykorzystane, na przykład, do wyświetlenia wskaźnika ładowania przed starym DOM-em podczas ładowania nowych komponentów.
Obsługa Błędów
<Suspense>
obecnie nie zapewnia obsługi błędów przez sam komponent - jednak możesz użyć opcji errorCaptured
lub hooka onErrorCaptured()
do przechwytywania i obsługi błędów asynchronicznych w komponencie nadrzędnym względem <Suspense>
.
Łączenie z Innymi Komponentami
Często zachodzi potrzeba używania <Suspense>
w połączeniu z komponentami <Transition>
i <KeepAlive>
. Kolejność zagnieżdżenia tych komponentów jest istotna dla ich prawidłowego działania.
Dodatkowo, komponenty te są często używane w połączeniu z komponentem <RouterView>
z Vue Router.
Poniższy przykład pokazuje, jak zagnieździć te komponenty, aby wszystkie działały zgodnie z oczekiwaniami. Dla prostszych kombinacji możesz usunąć komponenty, których nie potrzebujesz:
template
<RouterView v-slot="{ Component }"> <template v-if="Component"> <Transition mode="out-in"> <KeepAlive> <Suspense> <!-- główna zawartość --> <component :is="Component"></component> <!-- stan ładowania --> <template #fallback> Ładowanie... </template> </Suspense> </KeepAlive> </Transition> </template> </RouterView>
Vue Router posiada wbudowane wsparcie dla leniwego ładowania komponentów przy użyciu importów dynamicznych. Są one odmienne od komponentów asynchronicznych i obecnie nie będą wyzwalać <Suspense>
. Jednakże, mogą one posiadać komponenty asynchroniczne jako komponenty potomne, które mogą wyzwalać <Suspense>
w standardowy sposób.
Zagnieżdżony Suspense
- Wspierane tylko w 3.3+
Gdy mamy wiele komponentów asynchronicznych (typowe dla zagnieżdżonych lub opartych na układzie tras) jak tutaj:
template
<Suspense> <component :is="DynamicAsyncOuter"> <component :is="DynamicAsyncInner" /> </component> </Suspense>
<Suspense>
tworzy granicę, która zgodnie z oczekiwaniami rozwiąże wszystkie komponenty asynchroniczne w dół drzewa. Jednak gdy zmieniamy DynamicAsyncOuter
, <Suspense>
oczekuje na niego poprawnie, ale gdy zmieniamy DynamicAsyncInner
, zagnieżdżony DynamicAsyncInner
renderuje pusty węzeł do czasu jego rozwiązania (zamiast poprzedniego węzła lub slotu fallback).
Aby to rozwiązać, możemy użyć zagnieżdżonego suspense do obsługi aktualizacji zagnieżdżonego komponentu, na przykład:
template
<Suspense> <component :is="DynamicAsyncOuter"> <Suspense suspensible> <!-- ten --> <component :is="DynamicAsyncInner" /> </Suspense> </component> </Suspense>
Jeśli nie ustawisz właściwości suspensible
, wewnętrzny <Suspense>
będzie traktowany jak komponent synchroniczny przez nadrzędny <Suspense>
. Oznacza to, że ma on własny slot fallback i jeśli oba komponenty Dynamic
zmienią się w tym samym czasie, mogą pojawić się puste węzły i wiele cykli aktualizacji podczas gdy potomny <Suspense>
ładuje swoje własne drzewo zależności, co może być niepożądane. Gdy właściwość jest ustawiona, cała obsługa zależności asynchronicznych jest przekazywana do nadrzędnego <Suspense>
(włącznie z emitowanymi zdarzeniami), a wewnętrzny <Suspense>
służy wyłącznie jako dodatkowa granica dla rozwiązywania zależności i aktualizacji.
Powiązane