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
useQueryvon 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.
Server State
Abschnitt betitelt „Server State“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 einrichten
Abschnitt betitelt „Pinia Colada einrichten“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.
const app = createApp(App)
app.use(createPinia())// Pinia Colada keeps its cache in Pinia, so it comes after itapp.use(PiniaColada)app.use(router)Abfragen mit useQuery
Abschnitt betitelt „Abfragen mit useQuery“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.
// The answer stays in the cache under the key, a second visit shows it right awayconst { 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.
Ein Schlüssel pro Ding
Abschnitt betitelt „Ein Schlüssel pro Ding“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.
// A key per thing, a thing seen before comes from the cacheconst { data: item } = useQuery({ key: () => ['items', props.id], query: ({ signal }) => getJson(`/items/${props.id}`, { signal }),})Daten ändern mit useMutation
Abschnitt betitelt „Daten ändern mit useMutation“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.
const form = reactive({ from: '', to: '' })const queryCache = useQueryCache()
// After sending, the own requests are out of date, the cache loads them againconst { mutate: send, error: sendError } = useMutation({ mutation: (request) => postJson('/reservations', request), onSettled: () => queryCache.invalidateQueries({ key: ['reservations'] }),})TanStack Query
Abschnitt betitelt „TanStack Query“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.
Aufgaben
Abschnitt betitelt „Aufgaben“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.
npm installnpm testAufgabe 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
<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 awayconst { 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
// A key per thing, a thing seen before comes from the cacheconst { 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
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 againconst { 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
Abschnitt betitelt „Selbstcheck“Selbstcheck
- Worin unterscheidet sich Server State vom übrigen Zustand einer App?
- Wozu braucht
useQueryeinen Schlüssel? - Wann lädt Pinia Colada Daten von selbst neu?
- Was tut
invalidateQueriesnach einer Änderung?
Antworten
- Er gehört dem Server, andere ändern ihn ohne Wissen der App, und die Kopie im Browser kann veralten.
- Unter dem Schlüssel liegt die Antwort im Cache. Abfragen mit demselben Schlüssel teilen sich den Eintrag und eine laufende Anfrage.
- Wenn die Daten veraltet sind und eine Komponente sie wieder braucht, das Fenster den Fokus zurückbekommt oder das Netz zurückkehrt.
- 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.