Kurs Vue.js · Moduł 8: Sloty i komponenty dynamiczne

Async Components & Suspense

4 min czytania
W tej lekcji5

W NOVA LAB ciężkie moduły cargo nie są ładowane od razu - system ładuje je na żądanie, wyświetlając status na ekranie. Pomyśl o panelu z trójwymiarową mapą kraterów: jeśli jego kod trafi do głównej paczki aplikacji, każdy członek załogi zapłaci za niego czasem startu, nawet jeśli mapy nigdy nie otworzy. Vue oferuje async components - ładowanie komponentów lazy (na żądanie) i Suspense - kontrolę nad stanem ładowania.

defineAsyncComponent

Zamiast importować komponent bezpośrednio, możesz go załadować asynchronicznie. Funkcja defineAsyncComponent przyjmuje funkcję ładującą, która zwraca Promise z komponentem - najczęściej dynamiczny import(), dzięki któremu bundler, na przykład Vite, wydziela komponent do osobnego pliku:

1<script setup>
2import { defineAsyncComponent } from 'vue'
3
4// Prosty lazy import
5const HeavyModule = defineAsyncComponent(
6  () => import('./HeavyModule.vue')
7)
8</script>
9
10<template>
11  <HeavyModule />
12</template>

Komponent jest pobierany dopiero wtedy, gdy jest potrzebny, czyli przy pierwszym renderowaniu w template. Dla szablonu HeavyModule wygląda jak zwykły komponent, przyjmuje props i emituje zdarzenia - zmienił się tylko moment pobrania kodu.

Zaawansowana konfiguracja

Zamiast samej funkcji możesz przekazać obiekt opcji. Oprócz loader określasz w nim, co pokazać w trakcie ładowania, po jakim czasie to zrobić i co wyświetlić, gdy transport modułu się nie powiedzie:

1<script setup>
2import { defineAsyncComponent } from 'vue'
3import LoadingSpinner from './LoadingSpinner.vue'
4import ErrorDisplay from './ErrorDisplay.vue'
5
6const HeavyModule = defineAsyncComponent({
7  // Funkcja ładująca
8  loader: () => import('./HeavyModule.vue'),
9
10  // Komponent wyświetlany podczas ładowania
11  loadingComponent: LoadingSpinner,
12
13  // Opóźnienie przed wyświetleniem loading (ms)
14  delay: 200,
15
16  // Komponent wyświetlany gdy ładowanie się nie powiedzie
17  errorComponent: ErrorDisplay,
18
19  // Timeout - po tym czasie wyświetli error (ms)
20  timeout: 10000
21})
22</script>

Kolejność zdarzeń wygląda tak: Vue wywołuje loader, odczekuje delay, a jeśli moduł wciąż nie dotarł, pokazuje loadingComponent. Gdy import się zakończy, spinner zostaje zastąpiony właściwym komponentem. Wartość 200 ms to zresztą domyślne delay - na szybkim łączu spinner mignąłby tylko na ułamek sekundy. Domyślny timeout jest nieskończony, więc bez tej opcji errorComponent pojawi się tylko przy błędzie importu.

Suspense

<Suspense> to wbudowany komponent Vue do zarządzania stanem ładowania całego drzewa asynchronicznych komponentów naraz. Uwaga: w Vue 3.5 wciąż jest oznaczony jako funkcja eksperymentalna - jego API może się jeszcze zmienić, a Vue przypomina o tym komunikatem w konsoli. Ma dwa sloty, #default i #fallback:

1<template>
2  <Suspense>
3    <!-- Treść główna (ładowany komponent) -->
4    <template #default>
5      <HeavyModule />
6    </template>
7
8    <!-- Loading state -->
9    <template #fallback>
10      <div class="loading">
11        <p>Loading module...</p>
12        <div class="spinner"></div>
13      </div>
14    </template>
15  </Suspense>
16</template>

Przy pierwszym renderze Suspense renderuje treść #default w pamięci. Wtedy async komponent rozpoczyna ładowanie, a Suspense przechodzi w stan oczekiwania i wyświetla #fallback. Gdy wszystkie asynchroniczne zależności się zakończą, przełącza się na #default. Ważny szczegół: wewnątrz Suspense własne opcje loadingComponent, errorComponent, delay i timeout komponentu asynchronicznego są ignorowane, bo stanem ładowania steruje Suspense. Jeśli komponent ma zachować własny spinner, dodaj mu opcję suspensible: false.

Suspense events

Suspense emituje zdarzenia informujące o stanie ładowania: pending przy wejściu w stan oczekiwania, fallback przy wyświetleniu treści zastępczej i resolve, gdy treść główna jest gotowa:

1<template>
2  <Suspense
3    @pending="onPending"
4    @resolve="onResolve"
5    @fallback="onFallback"
6  >
7    <template #default>
8      <AsyncModule />
9    </template>
10    <template #fallback>
11      <LoadingSpinner />
12    </template>
13  </Suspense>
14</template>
15
16<script setup>
17const onPending = () => console.log('Loading started...')
18const onResolve = () => console.log('Loading complete!')
19const onFallback = () => console.log('Fallback displayed')
20</script>

Przy pierwszym ładowaniu konsola pokaże kolejno "Loading started...", "Fallback displayed" i "Loading complete!". To dobre miejsce na pomiar czasu ładowania albo wpis do dziennika stacji.

Async setup w komponentach

Komponent z async setup lub top-level await w <script setup> automatycznie staje się asynchroniczną zależnością dla Suspense:

1<!-- AsyncModule.vue -->
2<script setup>
3const data = await fetch('/api/module-data')
4  .then(r => r.json())
5</script>
6
7<template>
8  <div>{{ data.name }}</div>
9</template>

Taki komponent musi mieć nad sobą <Suspense>. Bez niego Vue wypisze ostrzeżenie i nie wyrenderuje go wcale. Suspense nie obsługuje też błędów samodzielnie - odrzucony fetch przechwycisz w komponencie nadrzędnym hookiem onErrorCaptured.

Moja rada: w kodzie produkcyjnym zacznij od defineAsyncComponent z loadingComponent i errorComponent, bo to stabilne API. Suspense stosuj tam, gdzie kilka komponentów ładuje się naraz i chcesz jednego wspólnego ekranu oczekiwania, pamiętając o jego eksperymentalnym statusie. W edytorze poniżej przycisk ładuje HeavyModule z symulowanym dwusekundowym opóźnieniem, a po 200 ms pojawia się LoadingSpinner.

Zapamiętaj: ciężki moduł dolatuje na stację dopiero wtedy, gdy ktoś go potrzebuje, a Suspense trzyma ekran oczekiwania, dopóki nie dotrze cały ładunek.

Kod do tej lekcji: App.vue
1<script setup>
2import { defineAsyncComponent, ref } from 'vue'
3import LoadingSpinner from './LoadingSpinner.vue'
4
5// Symulacja async component (w prawdziwej apce: import('./Heavy.vue'))
6const HeavyModule = defineAsyncComponent({
7  loader: () => new Promise((resolve) => {
8    setTimeout(() => {
9      resolve({
10        template: `
11          <div class="heavy-module">
12            <h2>Ciężki moduł załadowany!</h2>
13            <p>Złożona wizualizacja danych gotowa</p>
14            <div class="chart">
15              <div v-for="h in [80, 60, 90, 45, 75]" :key="h"
16                class="bar" :style="{ height: h + 'px' }">
17              </div>
18            </div>
19          </div>
20        `
21      })
22    }, 2000)
23  }),
24  loadingComponent: LoadingSpinner,
25  delay: 200
26})
27
28const showModule = ref(false)
29</script>
30
31<template>
32  <div class="nova-lab">
33    <h1>Async Components & Suspense</h1>
34
35    <button @click="showModule = !showModule" class="btn">
36      {{ showModule ? 'Ukryj' : 'Załaduj' }} ciężki moduł
37    </button>
38
39    <div v-if="showModule" class="container">
40      <HeavyModule />
41    </div>
42  </div>
43</template>
44
45<style scoped>
46.nova-lab {
47  background: #0a0e27;
48  color: #00ff88;
49  min-height: 100vh;
50  padding: 20px;
51  font-family: monospace;
52}
53h1 { color: #00ff88; margin-bottom: 20px; }
54.btn {
55  background: #00b4d8;
56  color: #0a0e27;
57  border: none;
58  padding: 12px 24px;
59  cursor: pointer;
60  font-family: monospace;
61  font-weight: bold;
62  font-size: 14px;
63}
64.container {
65  margin-top: 20px;
66  border: 2px solid #00b4d8;
67  padding: 20px;
68}
69</style>
70
71<style>
72.heavy-module h2 { color: #00ff88; margin-bottom: 10px; }
73.heavy-module .chart {
74  display: flex;
75  align-items: flex-end;
76  gap: 8px;
77  margin-top: 15px;
78  height: 100px;
79}
80.heavy-module .bar {
81  width: 30px;
82  background: #00b4d8;
83}
84</style>

Widzisz błąd w tej lekcji?

Sprawdź się

Odpowiedz na pytania z tej lekcji. Wybierz odpowiedź, a od razu zobaczysz, czy jest poprawna.

  1. 1. Do czego służy defineAsyncComponent w Vue?

  2. 2. Który slot w <Suspense> wyświetla treść podczas ładowania?

Zadania praktyczne w grze

  • Układanie w pionie

    Uporządkuj etapy działania Suspense:

  • Edytor kodu

    Użyj defineAsyncComponent do lazy loadingu komponentu HeavyModule. Wyświetl 'Loading...' podczas ładowania.

  • Układanie w poziomie

    Ułóż kod definicji async komponentu:

  • Klikanie w kolejności

    Ułóż kod Suspense z async komponentem:

  • Edytor kodu

    Użyj <Suspense> z #default i #fallback slotami do wyświetlenia async komponentu z loading spinnerem.

Przydatne artykuły