Zum Inhalt springen

Server-State-Bibliotheken

Dieses Kapitel ist ein Exkurs. Für die Prüfung brauchst du es nicht, und die Fallbeispiel-App kommt ohne die Bibliothek aus. Es zeigt aber, wie grössere Apps Daten vom Server verwalten, und in einem Projekt mit vielen Daten vom Server spart es viel Code.

Lernziele

Nach diesem Kapitel kannst du

  • erklären, was Server State ist und worin er sich vom übrigen Zustand unterscheidet,
  • Daten mit useQuery von Pinia Colada laden und zwischenspeichern,
  • einen Schlüssel wählen, der zu den Daten passt,
  • Daten mit useMutation ändern und veraltete Abfragen neu laden,
  • Pinia Colada und TanStack Query einordnen.

Die Daten vom Server sind im Browser nur eine Kopie. Die Dokumentation von TanStack Query beschreibt solchen Server State mit vier Eigenschaften. Er liegt an einem Ort, den die App nicht besitzt, die App erreicht ihn nur über asynchrone Zugriffe, andere können ihn ohne ihr Wissen ändern, und ihre Kopie kann veralten.

Ein Store wie in Kapitel 7.3 kümmert sich um all das selbst. Er lädt, merkt sich den Status, lädt nach einer Änderung neu und weiss nicht, ob eine andere Seite dieselben Daten gerade auch lädt. Bibliotheken für Server State übernehmen diese Arbeit und halten die Antworten in einem Cache.

Pinia Colada nennt sich selbst die Schicht für Datenabrufe in Vue. Es baut auf Pinia auf und hält seinen Cache in einem Store. Nach npm install @pinia/colada meldet main.js es nach Pinia an.

main.js
const app = createApp(App)
app.use(createPinia())
// Pinia Colada keeps its cache in Pinia, so it comes after it
app.use(PiniaColada)
app.use(router)

useQuery bekommt einen Schlüssel und eine Funktion, die die Daten holt. Unter dem Schlüssel legt Pinia Colada die Antwort in den Cache. Laut Dokumentation teilen sich alle Abfragen mit demselben Schlüssel denselben Eintrag, und gleichzeitige Anfragen gehen nur einmal an den Server.

CatalogView.vue
// The answer stays in the cache under the key, a second visit shows it right away
const { data: items, error, isPending } = useQuery({
key: ['items'],
query: ({ signal }) => getJson('/items', { signal }),
})

useQuery gibt unter anderem data, error und isPending zurück. Laut Quelltext gelten Daten fünf Sekunden lang als frisch. Danach sind sie veraltet, und Pinia Colada lädt sie neu, wenn eine Komponente sie wieder braucht, wenn das Fenster den Fokus zurückbekommt oder wenn das Netz zurückkehrt. Einträge, die keine Komponente mehr braucht, räumt es nach fünf Minuten weg. staleTime und gcTime ändern beides. Die Funktion bekommt ein signal, mit dem Pinia Colada eine überholte Anfrage abbricht.

Hängen die Daten von einem Wert ab, etwa von der Nummer eines Dings, gehört der Wert in den Schlüssel. Als Funktion geschrieben folgt der Schlüssel dem Wert, und jedes Ding bekommt seinen eigenen Eintrag. Ein Ding, das schon einmal da war, erscheint sofort aus dem Cache.

ItemView.vue
// A key per thing, a thing seen before comes from the cache
const { data: item } = useQuery({
key: () => ['items', props.id],
query: ({ signal }) => getJson(`/items/${props.id}`, { signal }),
})

Was Daten auf dem Server ändert, läuft über useMutation. Danach sind die betroffenen Abfragen veraltet, im Beispiel die eigenen Anfragen. invalidateQueries aus useQueryCache markiert sie als veraltet und lädt die Abfragen neu, die eine Komponente gerade zeigt.

ItemView.vue
const form = reactive({ from: '', to: '' })
const queryCache = useQueryCache()
// After sending, the own requests are out of date, the cache loads them again
const { mutate: send, error: sendError } = useMutation({
mutation: (request) => postJson('/reservations', request),
onSettled: () => queryCache.invalidateQueries({ key: ['reservations'] }),
})

Die zweite verbreitete Bibliothek für Server State ist TanStack Query, früher Vue Query, mit Paketen für Vue, React und weitere Frameworks. Für Vue heisst das Paket @tanstack/vue-query. Die Begriffe sind dieselben, useQuery nimmt dort queryKey und queryFn.

const { data, error, isPending } = useQuery({
queryKey: ['items'],
queryFn: () => getJson('/items'),
})

Laut Dokumentation gelten Daten bei TanStack Query sofort als veraltet, und eine Abfrage mit Fehler versucht es dreimal, bevor sie den Fehler zeigt. Für ein Projekt, das schon Pinia verwendet, liegt Pinia Colada nahe. TanStack Query kennt, wer auch mit React arbeitet.

Die Aufgaben stehen im Projekt exercises/07-state/server-state/de im Kursrepo. Die Tests brauchen keinen Server. Für die App im Browser startest du die Kurs-API und meldest dich mit dem Knopf im Kopf an.

Terminal-Fenster
npm install
npm test

Aufgabe Einstieg

CatalogView lädt den Katalog von Hand und fragt bei jedem Besuch die Kurs-API. Lade ihn mit useQuery unter dem Schlüssel ['items'] und zeig Laden, Fehler und Liste.

Lösung
CatalogView.vue
<script setup>
import { useQuery } from '@pinia/colada'
import { RouterLink } from 'vue-router'
import { getJson } from '@/api.js'
// The answer stays in the cache under the key, a second visit shows it right away
const { data: items, error, isPending } = useQuery({
key: ['items'],
query: ({ signal }) => getJson('/items', { signal }),
})
</script>
<template>
<h1>Katalog</h1>
<p v-if="isPending" role="status">Der Katalog wird geladen …</p>
<p v-else-if="error" class="error" role="alert">{{ error.message }}</p>
<ul v-else class="catalog">
<li v-for="item in items" :key="item.id">
<RouterLink :to="{ name: 'item', params: { id: item.id } }">{{ item.name }}</RouterLink>
<span class="category">{{ item.category }}</span>
</li>
</ul>
</template>

Kehrt jemand innerhalb von fünf Sekunden zum Katalog zurück, fragt Pinia Colada die API nicht noch einmal. Der Test zählt die Anfragen an /items.

Aufgabe Vertiefung

ItemView lädt das Ding bei jedem Wechsel neu. Lade es mit useQuery und einem Schlüssel aus 'items' und der Nummer.

Lösung
ItemView.vue
// A key per thing, a thing seen before comes from the cache
const { data: item } = useQuery({
key: () => ['items', props.id],
query: ({ signal }) => getJson(`/items/${props.id}`, { signal }),
})

Ohne Funktion bliebe der Schlüssel beim ersten Ding stehen, und die Seite zeigte nach einem Wechsel noch das alte Ding.

Aufgabe Knobelei

Schick die Anfrage mit useMutation und lade danach die eigenen Anfragen unter dem Schlüssel ['reservations'] neu, damit die Liste auf der Seite die neue Anfrage zeigt.

Lösung
ItemView.vue
const { data: requests } = useQuery({
key: ['reservations'],
query: ({ signal }) => getJson('/reservations', { signal }),
})
const form = reactive({ from: '', to: '' })
const queryCache = useQueryCache()
// After sending, the own requests are out of date, the cache loads them again
const { mutate: send, error: sendError } = useMutation({
mutation: (request) => postJson('/reservations', request),
onSettled: () => queryCache.invalidateQueries({ key: ['reservations'] }),
})
function submit() {
send({ itemId: props.id, ...form })
}

onSettled läuft nach Erfolg und nach Fehler. Auch eine abgelehnte Anfrage kann zeigen, dass die Liste veraltet ist, etwa wenn jemand anderes dieselben Tage schon angefragt hat.

Selbstcheck

  1. Worin unterscheidet sich Server State vom übrigen Zustand einer App?
  2. Wozu braucht useQuery einen Schlüssel?
  3. Wann lädt Pinia Colada Daten von selbst neu?
  4. Was tut invalidateQueries nach einer Änderung?
Antworten
  1. Er gehört dem Server, andere ändern ihn ohne Wissen der App, und die Kopie im Browser kann veralten.
  2. Unter dem Schlüssel liegt die Antwort im Cache. Abfragen mit demselben Schlüssel teilen sich den Eintrag und eine laufende Anfrage.
  3. Wenn die Daten veraltet sind und eine Komponente sie wieder braucht, das Fenster den Fokus zurückbekommt oder das Netz zurückkehrt.
  4. Es markiert die Abfragen mit dem Schlüssel als veraltet und lädt die neu, die eine Komponente gerade zeigt.

Texte und Abbildungen stehen unter CC BY-SA 4.0, der Code unter der MIT-Lizenz und die Daten des Kurses unter CC0 1.0. Die Schrift der Abbildungen ist Kalam unter der SIL Open Font License 1.1. Die Quellen liegen auf GitHub.