React-Deep-Dive: TanStack Router und TanStack Query – Teil 1
TanStack Router und Query bringen frischen Wind bei Routing und Data Fetching und eignen sich sogar als Alternative zu klassischen State-Management-Werkzeugen.
(Bild: erzeugt mit KI durch iX)
- Nils Hartmann
Der TanStack Router ist eine Alternative zum React Router, dem De-Facto-Standard für das Routing in React-Anwendungen. Die erste stabile Version veröffentlichte das TanStack-Team im Dezember 2023. Der Router bringt eine Reihe interessanter Konzepte mit, zum Beispiel eine durchgehende Typsicherheit, dateibasierte Routen und die Möglichkeit, Teile des Anwendungszustandes in die URL auszulagern.
Die Bibliothek TanStack Query (ehemals React Query) für das Data Fetching ist mittlerweile in Version 5 verfügbar und bereits sehr populär. Hervorzuheben sind ihre Flexibilität, ein ausgeklügelter Caching-Mechanismus und ebenfalls eine hohe Typsicherheit.
Die beiden Bibliotheken sind unabhängig voneinander verwendbar, arbeiten aber auch sehr gut zusammen und können in manchen Fällen sogar den Einsatz eines globalen State-Management-Werkzeugs wie Jotai, Zustand oder gar Redux ersetzen.
TanStack ist eine Sammlung von Open-Source-Bibliotheken für die Entwicklung von Single-Page-Anwendungen (SPA). Neben TanStack Router und TanStack Query gibt es beispielsweise TanStack Table und TanStack Form. Viele der Bibliotheken, die unter der "Marke" TanStack fungieren, werden für React, Angular, Vue und andere SPA-Bibliotheken angeboten. Der TanStack Router funktioniert allerdings nur mit React. Populär wurde TanStack mit den Bibliotheken Table und Query, die zunächst nur für React verfügbar waren und damals React Table und React Query hießen. Der Initiator der beiden Bibliotheken, Tanner Linsley, führte den Begriff TanStack ein, um zu verdeutlichen, dass die Bibliotheken inzwischen nicht mehr ausschließlich für React verfügbar sind.
TanStack Router und TanStack Query in Aktion
Zur Demonstration der beiden Bibliotheken dient hier eine React-Single-Page-Anwendung zur Verwaltung von Aufgaben (Tasks). Die Aufgaben lassen sich in einer sortierbaren Liste und in einer Einzel- oder Detaildarstellung anzeigen. AuĂźerdem lassen sich Aufgaben fĂĽr einen schnellen Zugriff anheften.
Die Anwendung ist sicherlich nicht vollständig und ließe sich mit diesem Funktionsumfang wahrscheinlich einfacher als klassische serverseitige Webanwendung bauen. Jedoch hätte eine solche Anwendung im echten Leben voraussichtlich weitere interaktive Features, die die Entwicklung als SPA rechtfertigen würden (zum Beispiel das Anlegen und Bearbeiten von Aufgaben). Der Fokus im Folgenden ist bewusst auf die Themen Navigation (Routing) und Arbeiten mit serverseitigen Daten gelegt. Der Sourcecode der Beispielanwendung ist auf GitHub verfügbar.
Der TanStack Router arbeitet dateisystembasiert, ähnlich wie von serverseitigen Routern bekannt. Dabei spielt die Struktur des Dateisystems, also die Organisation in Ordner und die Benennung von Dateien, eine wichtige Rolle. Per Konvention kann der Router daraus ableiten, welche URLs beziehungsweise Pfade die Anwendung besitzt und was beim Aufruf eines Pfades im Browser passieren soll. Dabei ist zu beachten, dass das Dateisystem nur zur Entwicklungs- und Build-Zeit eine Rolle spielt (dazu kommt ein Plug-in für Vite zum Einsatz). Zur Laufzeit handelt es sich beim TanStack Router um einen rein clientseitigen Router, genau wie beim React Router.
Sofern nicht anders konfiguriert, interpretiert der Router alle Dateien unterhalb der Verzeichnisses src/routes im Projekt als Routen (siehe Abbildung). Zur besseren Lesbarkeit entfällt das übergeordnete Verzeichnis in den folgenden Pfadangaben.
(Bild:Â Nils Hartmann)
Die Beispielanwendung soll im ersten Schritt über zwei Routen verfügen, die eine "Über uns"- (Route: "/about") und eine "Datenschutz"-Seite (Route: "/privacy") darstellen. Dazu sind zwei Dateien mit dem Namen der jeweiligen Route anzulegen. Das Vite-Plug-in erkennt diese Dateien und generiert Code zur Konfiguration der Route, der sich nach Belieben anpassen lässt. In der Regel gibt darin die component-Eigenschaft an, welche React-Komponente der Router beim Aufruf der Route im Browser rendern soll.
Das Listing zeigt den Code der (vereinfachten) Routen fĂĽr /about und /privacy:
// src/routes/about.tsx
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/about")({
component: AboutRouteComponent,
});
function AboutRouteComponent() {
return <div>About us { /* ... */ }</div>;
}
// src/routes/privacy.tsx
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/privacy")({
component: PrivacyRouteComponent,
});
function PrivacyRouteComponent () {
return <div>Privacy { /* ... */ }</div>;
}
Der Router unterstützt von Haus aus Code-Splitting: Das Vite-Plug-in lagert den JavaScript-Code einzelner Routen jeweils in ein eigenes Bundle aus. Dieses lädt der Browser erst dann, wenn die jeweilige Route aufgerufen wird (Lazy Loading). Dadurch ist es möglich, die Menge an JavaScript-Code, die der Browser initial beim Starten der Anwendung laden und ausführen muss, signifikant zu verkleinern und den Start der Anwendung erheblich zu beschleunigen. Per Konfiguration lässt sich das Code-Splitting automatisch für alle Routen aktivieren. Alternativ kann eine Routen-Datei mit der Dateiendung .lazy.tsx Code-Splitting für die Route aktivieren (zum Beispiel about.lazy.tsx).
Die /about-Route wird wie folgt dargestellt:
(Bild:Â Nils Hartmann)
Videos by heise
Typsichere Navigation
Die Link-Komponente des Routers erlaubt es, in der Anwendung von einer Route zur nächsten zu gelangen. Diese Komponente erwartet unter anderem die to-Property, die auf die Ziel-Route zeigt (ähnlich wie das href-Attribut am HTML-a-Element). Das nachfolgende Listing zeigt den Code für die Komponente GlobalNavBar, die die Navigationszeile der Anwendung darstellt. Hier werden zwei Links gerendert, die auf die About- beziehungsweise Privacy-Route zeigen. Die Property activeProps erlaubt das Setzen von Properties, die der Router nur dann anwendet, wenn der Link aktiv ist. Ein aktiver Link ist ein Link, der auf eine Route zeigt, die gerade gerendert ist. Damit lässt sich in einer Navigationszeile beispielsweise die aktuelle Route farblich hervorheben.
export default function GlobalNavBar() {
return (
<nav>
<Link
className={"AppLink"}
activeProps={{
className: "AppLink__active",
}}
to={"/about"}
>
About
</Link>
<Link
to={"/privacy"}
className={"AppLink"}
activeProps={{
className: "AppLink__active",
}}
>
Privacy
</Link>
</nav>
);
}
Eine Besonderheit des Routers ist seine konsequente Typsicherheit. Die to-Property kennt alle Routen der Anwendung und akzeptiert nur gültige Werte, also Routen, die tatsächlich existieren. Zum Eintragen der korrekten Werte kann TypeScript beispielsweise mit Codevervollständigung unterstützen, wie die Abbildung zeigt. Die entsprechenden Typen dafür erzeugt das Vite-Plug-in automatisch. Das stellt sicher, dass die Typen zu den tatsächlich vorhandenen Routen im Projekt passen und nicht zum Beispiel nach einem Refactoring auseinanderlaufen.
(Bild:Â Nils Hartmann)
Mit TanStack Query serverseitige Daten abfragen
Die Beispielanwendung soll zwei weitere Routen erhalten, die eine Liste von Aufgaben beziehungsweise eine einzelne Aufgabe darstellen. Zur Illustration, wie die Anwendung dafür die Daten vom Backend über eine REST API lädt, soll zunächst nur die Listendarstellung dienen. Diese soll sich über den Pfad "/tasks" aufrufen lassen (siehe Abbildung). Dazu legt man ein neues Verzeichnis mit dem Namen der Route (tasks) an. Es enthält die Datei index.tsx, in der sich die Routenkonfiguration befindet. Auch hier generiert das Vite-Plug-in den erforderlichen Boilerplate-Code und erweitert auch die TypeScript-Typen, sodass die GlobalNavBar-Komponente nun auch einen Link auf die tasks-Route enthalten kann, ohne einen Compile-Fehler auszulösen.
(Bild:Â Nils Hartmann)
Die Daten in der Tasks-Komponente soll TanStack Query lesen. Um die Funktionsweise von TanStack Query isoliert vom Router zu zeigen, liest es die Daten hier exemplarisch direkt in der Komponente. Ein späteres Beispiel wird dafür die Loader-Funktion einer Route verwenden.
TanStack Query bietet mehrere Wege, um Server Requests auszuführen und Daten zu lesen. Zentrales Objekt dafür ist der beim Start der Anwendung erzeugte QueryClient. Er verwaltet den Cache und andere globale Einstellungen. Außerdem stellt er eine React-unabhängige API zum Ausführen von Queries zur Verfügung. Innerhalb von React-Komponenten wird der QueryClient in der Regel aber nicht direkt verwendet. Stattdessen kommen hier React-spezifische Hook-Funktionen wie useQuery und useSuspenseQuery zum Einsatz. Exemplarisch wird hier zunächst der useQuery-Hook gezeigt. Die Verwendung des Suspense-Features von React mit TanStack Query wird der zweite Teil des Artikels zeigen.
Unabhängig davon, über welchen Weg Entwicklerinnen und Entwickler eine Query ausführen möchten, müssen sie dafür mindestens zwei Einstellungen vornehmen, die Query Options. Zum einen geben sie eine Funktion – die Query Function – an, die für das Laden der Daten zuständig ist. Zum anderen muss ein Query Key definieren, wo die gelesenen Daten im Cache abzulegen sind. Das Listing zeigt eine vereinfachte Verwendung von useQuery:
function TaskListRouteComponent() {
const result = useQuery({
queryKey: ["tasks", "list"],
queryFn(): Promise<Task[]> {
return fetch("http://api.tasks").then(res => res.json());
},
});
if (result.isPending) {
// Daten werden noch gelesen
return <PlaceholderTaskTable />
}
if (result.isSuccess) {
// Daten wurden erfolgreich gelesen
return <TaskTable tasks={result.data} />
}
// Fehler
return "Die Tasks konnten nicht gelesen werden";
}
Der Kontrakt der Query-Funktion ist denkbar einfach: Sie muss ein Promise zurückliefern, das die gelesenen Daten enthält. Kann die Funktion die Daten nicht lesen, zum Beispiel weil es ein Problem mit dem Netzwerk gibt oder der angefragte Endpunkt nicht funktioniert, muss sie einen Fehler werfen. Der Query Key dient dazu, die gelesenen Daten in den Cache zu schreiben und später wieder auszulesen.
Wird die tasks-Route aufgerufen, rendert der Router die TaskListRouteComponent und die Query wird ausgeführt. Sofern noch keine Daten im Cache liegen, liefert die useQuery-Funktion ein Objekt zurück, in dem die "isPending"-Information auf true gesetzt ist. Die Komponente zeigt dann einen Platzhalter an. Nach dem erfolgreichen Lesen der Daten wird die Komponente neu gerendert – das entspricht dem typischen React-Verhalten bei einer Änderung des State einer Komponente.
Das von useQuery zurückgelieferte Objekt enthält jetzt unter anderem die Information "isSuccess", die angibt, dass die Daten vorliegen, die dann mittels "data" abrufbar sind. Über diese (und weitere) Statusinformationen kann eine Komponente also genau auf die einzelnen Phasen eines Lebenszyklus des Request reagieren und passende Informationen an der Oberfläche ausgeben.
Die gelesenen Daten legt TanStack Query in seinem globalen Cache ab. Beim Rendern der Komponente prĂĽft die Bibliothek, ob sich die Daten im Cache befinden (anhand des Query Key). Sofern sie dort schon vorhanden sind, werden die Daten aus dem Cache zurĂĽckgeliefert. In der Beispielanwendung fĂĽhrt das dazu, dass beim Wechseln zwischen der Task-Liste und einer anderen Route und wieder zurĂĽck zur Task-Liste die Daten direkt aus dem Cache kommen. Auch wenn mehrere gleichzeitig dargestellte Komponenten die Daten fĂĽr denselben Query Key abfragen, stellt TanStack Query sicher, dass die Query nicht mehrfach ausgefĂĽhrt wird und dass alle Komponenten den gleichen Stand aus dem Cache erhalten.
Standardmäßig bleiben die gelesenen Daten im Cache, werden aber sofort als "stale", also veraltet betrachtet. Das führt dazu, dass die Komponente die Daten beim erneuten Abfragen zwar aus dem Cache zurück erhält, parallel dazu aber im Hintergrund die Query Function erneut läuft, um die Daten im Cache zu aktualisieren. Die Darstellung der Daten kann somit beim Wechseln zwischen den Routen grundsätzlich sehr schnell erfolgen. Sobald die Query im Hintergrund neue Daten gelesen hat, aktualisiert sich die Darstellung automatisch. Wie lange die Daten im Cache bleiben, wann sie als veraltet gelten und unter welchen Umständen sie erneut gelesen werden, lässt sich sehr flexibel global und pro Query einstellen, um nahezu alle erdenklichen Anforderungen damit abzubilden.
(Bild:Â WD Ashari/Shutterstock.com)
Die enterJS 2025 findet am 7. und 8. Mai in Mannheim statt. Die Konferenz bietet einen umfassenden Blick auf die JavaScript-gestĂĽtzte Enterprise-Welt. Der Fokus liegt nicht nur auf den Programmiersprachen JavaScript und TypeScript selbst, sondern auch auf Frameworks und Tools, Accessibility, Praxisberichten, UI/UX und Security.
Highlights aus dem Programm:
- Moderne React-Anwendungen mit TanStack
- 4 kritische Antipatterns in der React/TypeScript-Entwicklung
- React: Single-Page- oder Fullstack-Anwendung (Workshop, 6. Mai)
Tickets sind zum Frühbucherpreis im Online-Shop erhältlich.