Blog

React-Überblick: Von JSX zur Komponentenarchitektur

Ein kompletter Durchgang durch React, wie ich es heute unterrichte – vom deklarativen Rendern über die Frage, wem ein Zustand gehört, bis zur Architektur einer wachsenden Anwendung. Die Details zu einzelnen Stationen kommen später als eigene Beiträge.

≈ 15 Min. Lesezeit

Diesen Beitrag anhören (20 Min.)

MP3 herunterladen

React nimmt einem das Rendern ab, nicht die Architektur. Die Bibliothek berechnet aus einem Zustand eine Oberfläche und schreibt die Unterschiede ins DOM – aber wo dieser Zustand liegt, wie lange er lebt und wer ihn ändern darf, entscheidet sie an keiner Stelle. Genau dort, an dieser unscheinbaren Grenze, entstehen die Probleme, die sich später mit keinem Hook und keiner Bibliothek mehr wegräumen lassen.

Dieser Beitrag geht React deshalb einmal am Stück durch – vom ersten Stück JSX bis zu der Stelle, an der aus einzelnen Komponenten ein System wird. Es ist bewusst ein Überblick. Zu den meisten Stationen, die ich hier nur kurz betrete, folgen später eigene Beiträge, die tiefer graben. Heute geht es darum, den großen Bogen im Kopf zu haben, bevor man sich in ein Detail verliert.

Ich stütze mich dabei auf zwei Repositories, die ich gerade zusammenführe: introduction-react für die Grundlagen und advanced-react für die fortgeschrittenen Muster. Beide sind kein Foliensatz in Textform, sondern lauffähiger Code, an dem wir gemeinsam üben. Der Stand, den ich hier beschreibe, ist React 19 – die stabile Version von Ende 2024, mit allem, was seither zum Alltag geworden ist.

Das mentale Modell: die Oberfläche ist eine Funktion des Zustands

Bevor eine einzige Zeile JSX Sinn ergibt, muss ein Gedanke sitzen. React dreht die Art um, wie man über eine Oberfläche nachdenkt. Statt Schritt für Schritt zu beschreiben, wie sich das DOM verändern soll – hier ein Element einfügen, dort ein Attribut ändern, da einen Knoten entfernen –, beschreibt man nur, wie die Oberfläche für einen gegebenen Zustand aussehen soll. React kümmert sich um den Weg dorthin.

Man kann das in eine einzige Zeile fassen, die den ganzen Rest trägt: Die Oberfläche ist eine Funktion des Zustands. Ändert sich der Zustand, ruft React die Funktion erneut auf, vergleicht das Ergebnis mit dem vorherigen und schreibt nur die tatsächlichen Unterschiede ins echte DOM. Dieser Abgleich läuft über eine interne, leichtgewichtige Repräsentation des Baums, oft Virtual DOM genannt. Der Name ist weniger wichtig als die Konsequenz: Man beschreibt das Ziel, nicht den Weg. Wie dieser Abgleich Schritt für Schritt abläuft, zeige ich ausführlich im Beitrag Wie React den Baum rendert und aktualisiert.

flowchart LR
  State["Zustand (state)"] --> Render["Komponente rendert"]
  Render --> View["beschriebene Oberfläche"]
  View --> DOM["React gleicht ab und schreibt ins DOM"]
  User["Nutzerinteraktion"] --> Event["Event / setState"]
  Event --> State

Der zweite tragende Gedanke ist der unidirektionale Datenfluss. Daten fließen von oben nach unten, von der Elternkomponente zum Kind, über Props. Ereignisse fließen in die andere Richtung, als Callbacks nach oben. Es gibt keine geheimnisvolle Zweiwegbindung, bei der ein Eingabefeld und eine Variable magisch synchron bleiben. Das wirkt am Anfang umständlicher als in älteren Frameworks, aber es ist der Grund, warum sich React-Anwendungen auch nach zwei Jahren noch erklären lassen: Man kann für jede Stelle der Oberfläche sagen, woher ihre Daten kommen und wer sie ändern darf.

JSX und Komponenten: die kleinste Einheit ist eine Funktion

JSX sieht aus wie HTML im JavaScript, ist aber nur eine bequeme Schreibweise für Funktionsaufrufe. Ein <button> wird zu einem Aufruf, der ein Objekt beschreibt, kein DOM-Element erzeugt. Deshalb kann man mitten im JSX rechnen, Listen abbilden und bedingt rendern – es ist ja alles nur JavaScript:

function InvoiceList({ invoices }) {
  if (invoices.length === 0) return <p>No invoices.</p>;

  return (
    <ul>
      {invoices.map((i) => (
        <li key={i.id}>
          {i.number} - {i.paid ? "paid" : "open"}
        </li>
      ))}
    </ul>
  );
}

Drei Details in diesem kleinen Beispiel sind in Wahrheit Architekturthemen. Das key-Attribut ist kein kosmetisches Detail, sondern die Information, an der React beim erneuten Rendern erkennt, welches Listenelement welchem entspricht. Das bedingte Rendern mit ? oder mit einem frühen return ist die Stelle, an der Lade-, Fehler- und Leerzustände sichtbar werden – genau die Zustände, die im Demo-Modus gern unterschlagen werden und im Betrieb dann fehlen. Und props ist eine schreibgeschützte Schnittstelle: Eine Komponente bekommt ihre Daten von außen und darf sie nicht verändern. Das macht Komponenten zu Bausteinen, deren Verhalten man von außen versteht. Die genauen Regeln von JSX – was Ausdruck ist, was beim Rendern verschwindet, wie Keys wirklich funktionieren – behandle ich im Detail im Beitrag JSX genau: Die Regeln hinter dem Markup.

Der Standard ist heute die Funktionskomponente. Sie ist eine Funktion, die Props entgegennimmt und JSX zurückgibt, und mit Hooks kann sie inzwischen alles, wofür man früher Klassen brauchte. Klassenkomponenten mit render-Methode und Lifecycle-Methoden wie componentDidMount existieren noch, und man trifft sie in älterem Code. Wer sie in einem gewachsenen Projekt vor sich hat, muss nicht erschrecken – sie sind eine historische Schicht, mehr nicht. Aber neuer Code entsteht mit Funktionen. Das ist keine Frage der Mode, sondern eine echte Vereinfachung: Ein Effekt, der beim Einhängen und beim Aushängen etwas tun soll, war früher über zwei getrennte Lifecycle-Methoden verstreut. Heute steht er an einer Stelle.

Komposition ersetzt Vererbung. Statt eine Basiskomponente zu erweitern, steckt man Komponenten ineinander und reicht Inhalt über children durch:

function Card({ title, children }) {
  return (
    <section className="card">
      <h2>{title}</h2>
      {children}
    </section>
  );
}

Eine solche Card weiß nichts über ihren Inhalt und ist gerade deshalb überall verwendbar. Wer diesen Reflex verinnerlicht – lieber zusammenstecken als vererben –, schreibt fast automatisch entkoppelte Oberflächen.

Zustand ist kein Ort

Jetzt kommt die Frage vom Anfang, und sie ist die wichtigste im ganzen Beitrag. Zustand ist kein Ort im Code, sondern eine Entscheidung über Besitz, Lebensdauer und Synchronisation. Bevor man useState schreibt, sollte man wissen, wem dieser Zustand eigentlich gehört.

Der lokale Fall ist der einfachste. Ein Zähler, ein aufgeklapptes Menü, der Text in einem Eingabefeld – solcher Zustand gehört in die Komponente, die ihn anzeigt:

import { useState } from "react";

function Counter({ start = 0 }) {
  const [count, setCount] = useState(start);
  return <button onClick={() => setCount((n) => n + 1)}>{count}</button>;
}

Die Regel, die React an dieser Stelle einfordert, heißt: Komponenten rein halten. Für dieselben Eingaben soll dieselbe Ausgabe herauskommen. Der Zustand wird nicht direkt verändert, sondern über die Setter-Funktion ersetzt, und React entscheidet, wann neu gerendert wird. Wer das ernst nimmt, bekommt Komponenten, die man ohne laufenden Browser im Kopf durchspielen kann.

Interessant wird es, sobald ein Zustand nicht mehr in eine einzelne Komponente passt. Dann muss man ehrlich beantworten, wo er hingehört, und die Antwort ist selten „in einen globalen Store“. Ich unterscheide vier Orte, und die Wahl zwischen ihnen ist die eigentliche Architekturarbeit im Frontend:

flowchart TB
  Frage["Wem gehört dieser Zustand?"]
  Frage --> Lokal["Komponente<br/>(useState) – UI-Detail"]
  Frage --> URL["URL<br/>Filter, Seite, ausgewähltes Objekt"]
  Frage --> Server["Server-Cache<br/>geladene Daten, die dem Backend gehören"]
  Frage --> Global["globaler Store<br/>app-weite, geteilte Entscheidungen"]

Vieles, was Teams reflexhaft in einen globalen Store legen, gehört eigentlich in die URL – welcher Filter aktiv ist, welche Seite angezeigt wird, welches Objekt ausgewählt ist. In der URL ist dieser Zustand teilbar, mit einem Lesezeichen speicherbar und überlebt einen Reload. Und ein großer Teil dessen, was wie Anwendungszustand aussieht, ist in Wahrheit gar keiner: Es sind Daten, die dem Server gehören und die man nur zwischengespeichert hat. Diese Unterscheidung zwischen echtem Client-Zustand und gecachten Serverdaten ist die folgenreichste im ganzen Frontend, und ich widme ihr einen eigenen Beitrag: Client-Zustand vs. Server-Cache.

Effekte sind der Rand, nicht die Mitte

useEffect ist der Hook, der in den Teams, mit denen ich gearbeitet habe, die meisten Missverständnisse produziert. Er synchronisiert eine Komponente mit etwas außerhalb von React – einem Netzwerkaufruf, einem Abonnement, dem Titel des Browsertabs, einem Timer. Der entscheidende Satz lautet: Ein Effekt ist der Rand des Systems, nicht seine Mitte. Alles, was sich aus dem Zustand berechnen lässt, gehört nicht in einen Effekt, sondern direkt ins Rendern.

import { useState, useEffect } from "react";

function useInvoice(id) {
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);

  useEffect(() => {
    let active = true;
    fetch(`/api/invoices/${id}`)
      .then((r) => r.json())
      .then((d) => active && setData(d))
      .catch((e) => active && setError(e.message));
    return () => {
      active = false;
    };
  }, [id]);

  return { data, error };
}

Zwei Dinge an diesem Beispiel sind die eigentliche Lektion. Das Abhängigkeits-Array [id] bestimmt, wann der Effekt erneut läuft – vergisst man dort einen Wert, arbeitet die Komponente mit veralteten Daten weiter. Und die zurückgegebene Aufräumfunktion, die active auf false setzt, verhindert, dass eine spät eintreffende Antwort einen bereits ausgehängten oder inzwischen überholten Zustand überschreibt. Genau dieser Wettlauf zwischen zwei Anfragen ist der Fehler, der im Test mit schnellen Antworten nie auftritt und erst im Betrieb mit langsamem Netz zuschlägt.

Ich habe die Logik hier bewusst in einen eigenen Custom Hook useInvoice gezogen. Damit sind wir schon beim heutigen Weg, Logik wiederzuverwenden – dazu gleich mehr. Wichtig ist an dieser Stelle nur die Haltung: Effekte begrenzen, nicht häufen. Ein Frontend, in dem Datenladen, Ableitung, Synchronisation und Nutzerereignisse in einem einzigen Effektklumpen landen, ist fragil, weil niemand mehr sagen kann, warum welcher Effekt wann läuft.

Der häufigste Fehler, den ich in Code-Reviews sehe, ist genau der umgekehrte Reflex: einen Wert, der sich aus anderem Zustand berechnen lässt, über einen Effekt in einen zweiten Zustand zu spiegeln. Wer den vollständigen Namen aus Vor- und Nachnamen per Effekt in eine eigene Variable schreibt, hat sich zwei Quellen der Wahrheit gebaut, die auseinanderlaufen, sobald ein Rendern dazwischenkommt. Der berechnete Wert gehört nicht in einen Effekt und auch nicht in einen zweiten useState, sondern schlicht ins Rendern, wo er bei jedem Durchlauf frisch entsteht. Was ableitbar ist, wird abgeleitet und nicht gespeichert – diese eine Regel erspart mehr Fehler als jede Bibliothek. Ein Effekt ist erst dann berechtigt, wenn wirklich etwas außerhalb von React im Spiel ist.

Für das Laden von Serverdaten muss man diesen Mechanismus in echten Projekten übrigens selten von Hand schreiben. In der Praxis übernimmt eine darauf spezialisierte Bibliothek das Zwischenspeichern, das erneute Laden und die Konsistenz. React 19 geht mit dem use()-Hook und Suspense noch einen Schritt weiter und erlaubt es, ein Promise direkt im Rendern abzuwarten, sodass Lade- und Fehlerzustände weiter nach außen an eine umschließende <Suspense>-Komponente wandern. Dieses eigene große Thema behandle ich im Beitrag Suspense und der use-Hook.

Von HOCs und Render Props zu Custom Hooks

Die fortgeschrittenen React-Muster, die im advanced-react-Material einen eigenen Block bilden, sind fast alle Antworten auf dieselbe Frage: Wie teile ich Logik zwischen Komponenten, ohne sie zu kopieren? Die Antwort hat sich über die Jahre verschoben, und dieser Wandel ist selbst lehrreich.

Früher gab es dafür zwei etablierte Muster. Eine Higher-Order Component ist eine Funktion, die eine Komponente entgegennimmt und eine erweiterte zurückgibt – ein Wrapper, der Verhalten hinzufügt. Render Props lösen dasselbe Problem, indem eine Komponente ihre Logik über eine Funktion als Kind nach außen gibt. Beide funktionieren, aber beide haben denselben Preis: Sie verschachteln den Komponentenbaum und machen aus einer flachen Idee eine tiefe Struktur.

Custom Hooks lösen genau dieses Problem, ohne den Baum zu verschachteln. Ein Custom Hook ist einfach eine Funktion, deren Name mit use beginnt und die andere Hooks benutzt. Das useInvoice von oben ist bereits einer. Die Logik steht in einer normalen Funktion, wird von mehreren Komponenten aufgerufen und taucht im Baum überhaupt nicht auf:

function InvoiceView({ id }) {
  const { data, error } = useInvoice(id);
  if (error) return <ErrorMessage text={error} />;
  if (!data) return <LoadingIndicator />;
  return <InvoiceDetails invoice={data} />;
}

Dieselbe Bewegung findet man beim Kontext. Reichen Daten über viele Ebenen durch, ohne sie durch jede Zwischenkomponente als Prop zu fädeln, ist das die Aufgabe von Context: ein Provider oben im Baum, ein useContext unten. Der wichtige Hinweis dabei: Context ist zum Verteilen gedacht, nicht als Ersatz für einen durchdachten Zustandsbesitz. Wer jeden veränderlichen Wert in einen Context legt, verlagert das Kopplungsproblem nur eine Ebene höher. Auch das – Context als Verteilweg gegen Context als heimlicher globaler Zustand – behandle ich im Beitrag Context richtig einsetzen.

Wenn geteilter Zustand wirklich global ist: Redux und die Alternativen

Manchmal ist Zustand tatsächlich app-weit und wird von vielen, weit auseinanderliegenden Stellen gelesen und geändert. Für diesen Fall gibt es etablierte Werkzeuge, und Redux ist das bekannteste. Sein Kern ist erstaunlich klein: Der gesamte Zustand liegt in einem Store, geändert wird er nur durch das Verschicken einer Action – einem einfachen Objekt mit einem type –, und ein Reducer ist eine pure Funktion, die aus altem Zustand und Action den neuen Zustand berechnet.

function invoicesReducer(state = [], action) {
  switch (action.type) {
    case "invoice/paid":
      return state.map((i) =>
        i.id === action.id ? { ...i, paid: true } : i
      );
    default:
      return state;
  }
}

Der Reiz dieses Musters ist, dass jede Zustandsänderung ein benanntes, protokollierbares Ereignis wird. Man kann die Abfolge der Actions nachvollziehen, aufzeichnen und im Test durchspielen. Der Preis ist eine gewisse Menge an Struktur. Mir sind in Projekten drei Arten begegnet, diese Struktur zu organisieren, und die Wahl entscheidet darüber, ob ein UI-System fachlich oder technisch wächst: den Rails-Stil mit getrennten Ordnern für Actions, Reducer und Container; den Domain-Stil, der stattdessen pro Feature bündelt; und das Ducks-Muster, das Actions und Reducer einer Sache in eine Datei legt. Der Domain-Schnitt ist fast immer der bessere, weil er die Datei-Grenzen entlang der Fachlichkeit zieht und nicht entlang technischer Rollen.

Heute schreibt kaum jemand Redux mehr von Hand so aus. Das offizielle Redux Toolkit nimmt einem den größten Teil der Wiederholung ab, und für viele Anwendungen reicht eine leichtere Bibliothek oder schlicht Context mit einem Reducer. Reaktive Ansätze wie MobX verfolgen dasselbe Ziel mit einem anderen Modell, bei dem sich abgeleitete Werte automatisch aktualisieren. Welches dieser Werkzeuge passt, hängt weniger von der Mode ab als von der ehrlichen Antwort auf die Besitzfrage von vorhin – dem widme ich den eigenen Vergleich State-Management in React: Redux Toolkit, leichte Stores, MobX.

Ich mache in Architektur-Diskussionen gern die Gegenprobe: Für wie viel des vermeintlich globalen Zustands braucht man den Store wirklich? Meistens schrumpft die Liste dramatisch, sobald man die URL für Auswahl und Filter nutzt und geladene Serverdaten als das behandelt, was sie sind – Cache, nicht Zustand. Übrig bleibt oft nur ein kleiner Kern echter, app-weiter Entscheidungen: die angemeldete Person, das gewählte Sprach- oder Theme-Setting, vielleicht ein Warenkorb. Für diesen Kern lohnt sich ein Store, für den Rest ist er Ballast. Ein globaler Store ist also nicht die Standardantwort, sondern die Ausnahme, die man begründen können muss.

Router, Server und die Ränder der Anwendung

Zwei Ränder fehlen noch. Der erste ist die Navigation. In einer Single-Page-Anwendung wechselt man nicht die Seite, sondern bildet URLs auf Komponenten ab. Der Router hält Adresszeile und angezeigte Komponente synchron, sodass Vor- und Zurück-Knopf, Lesezeichen und geteilte Links weiter funktionieren:

import { BrowserRouter, Routes, Route } from "react-router-dom";

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Start />} />
        <Route path="/invoices/:id" element={<InvoiceView />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

Der Router ist damit auch der natürliche Ort für den Zustand, der in die URL gehört. Der Parameter :id, den man in der Komponente über useParams ausliest, ist genau das ausgewählte Objekt, über das ich weiter oben gesprochen habe – kein Grund, es zusätzlich in einem Store zu halten. Für neue Projekte empfiehlt React Router inzwischen die Data-Router-Variante mit createBrowserRouter, bei der Routen zusätzlich Lade- und Schreibfunktionen bekommen; das ist der modernere Weg, dem ich den Beitrag React Router Data Router widme.

Der zweite Rand ist das Rendern auf dem Server. Für Startzeit, Suchmaschinen und schwache Geräte ist es oft sinnvoll, die erste Fassung der Seite auf dem Server zu erzeugen und im Browser nur noch lebendig zu machen. Frameworks wie Next.js nehmen einem diese Mechanik ab. React 19 hat das Thema mit den Server Components noch einmal grundlegend erweitert: Komponenten können auf dem Server laufen, dort Daten laden und nur ihr Ergebnis an den Browser schicken, während die Server Actions das Gegenstück für schreibende Vorgänge bilden. Das verschiebt die alte Grenze zwischen Client und Server spürbar, und es ist ein so großes Thema, dass ich ihm den Beitrag React Server Components und Server Actions widme; hier steht es nur als Wegweiser.

Betrieb: Tests, Werkzeugkette, Typen

Ein Überblick, der beim Rendern aufhört, wäre unvollständig, denn all die Grenzen, die ich beschrieben habe – Zustandsbesitz, Effekt-Disziplin, Datenfluss –, lassen sich unbemerkt kaputtmachen, wenn niemand eine Erwartung daran festhält. Tests sind im Frontend keine Pflichtübung, sondern der Ort, an dem diese Erwartungen sichtbar bleiben.

Ich teste Komponenten heute mit Vitest und der React Testing Library, und deren Leitidee passt genau zur Architektur dieses Beitrags: Man prüft, was Nutzer sehen und tun, nicht wie die Komponente innen gebaut ist. Ein Test, der an das sichtbare Ergebnis gebunden ist, überlebt ein Refactoring; ein Test, der an Implementierungsdetails klebt, bricht bei jeder Umbenennung.

import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Counter } from "./Counter";

describe("Counter", () => {
  it("counts up on click", async () => {
    render(<Counter start={0} />);
    const button = screen.getByRole("button", { name: "0" });
    await userEvent.click(button);
    expect(screen.getByRole("button", { name: "1" })).toBeInTheDocument();
  });
});

Dahinter steht dieselbe Testpyramide wie überall: viele schnelle Komponententests an der Basis, weniger Integrationstests in der Mitte, eine Handvoll Ende-zu-Ende-Tests mit einem Werkzeug wie Playwright an der Spitze. Wer diese Verteilung auf den Kopf stellt, bezahlt es später mit einer trägen Build-Pipeline.

Auch die Werkzeugkette ist eine Architekturentscheidung, keine Geschmacksfrage. Ob ein Projekt mit Vite, Next.js, Parcel oder einer handgeschriebenen Webpack-Konfiguration startet, legt fest, wie schnell die Rückmeldung beim Entwickeln ist, wie einfach das Server-Rendern wird und wie viel man selbst wartet. Und über all dem liegt TypeScript, das aus Props und Zustand überprüfbare Verträge macht. Ein falsch getippter Prop wird dann nicht zum Laufzeitfehler beim Nutzer, sondern zum roten Kringel im Editor. Der React Compiler, den das React-Team als eigenes Projekt neben der Bibliothek entwickelt, nimmt einem schließlich einen Teil der manuellen Optimierung ab: Er analysiert Komponenten beim Bauen und merkt sich automatisch, was sich zwischen zwei Renderdurchläufen nicht geändert hat, sodass viele Aufrufe von useMemo, useCallback und memo überflüssig werden.

Das Repository ist selbst schon Architektur

Ein Blick zum Schluss noch auf die Struktur der Repositories selbst. introduction-react und advanced-react sind nicht ein großer Klumpen, sondern viele kleine, eigenständige Beispiele – eine Basis-App, eine Redux-App, eine Router-App, eine mit Server-Rendern, dazu die fortgeschrittenen Ordner für asynchrone Muster, funktionale Komposition und Struktur. Jedes Beispiel steht für sich und lässt sich einzeln starten.

Dass ich die beiden Repositories gerade zusammenlege, ist deshalb selbst eine kleine Architekturübung. Die Frage ist dieselbe wie bei jedem wachsenden System: Was darf geteilt werden, und was muss getrennt bleiben, damit eine Änderung an einer Stelle nicht überraschend an einer anderen etwas kaputt macht? Ein sauber geschnittenes Monorepo aus Grundlagen- und Fortgeschrittenen-Beispielen löst genau dieselbe Aufgabe wie eine Anwendung, die aus vielen Komponenten und Modulen besteht. Die Grenzen, die man zieht, sind an beiden Stellen die eigentliche Arbeit.

flowchart TB
  Repo["React-Monorepo"]
  Repo --> Grund["Grundlagen<br/>JSX, Komponenten, Hooks, Router"]
  Repo --> Fort["Fortgeschritten<br/>async, funktional, Struktur"]
  Repo --> Geteilt["geteilte Bausteine<br/>UI-Komponenten, Testhilfen"]
  Grund --> Geteilt
  Fort --> Geteilt

Was am Ende bleiben soll

Von so einem Durchgang bleibt im besten Fall nicht der Satz „ich kenne jetzt die Hooks“ übrig, sondern ein Reflex: bei jedem Zustand zuerst zu fragen, wem er gehört und wie lange er leben soll. Genau das ist der Kern. React gibt einem ein sehr gutes Werkzeug, um Oberflächen zu beschreiben, aber es trifft die Architekturentscheidungen nicht für einen. Wem ein Zustand gehört, wo ein Effekt seinen Rand hat, welcher Zustand in der URL, im Server-Cache oder im Store liegt – das bleibt die Arbeit, die ein Mensch machen muss.

Deshalb ist dieser Beitrag ein Überblick und kein Handbuch. Ich habe viele Stationen nur kurz betreten: den Unterschied zwischen Client-Zustand und Server-Cache, Context als Verteilweg, die Wahl zwischen Redux und seinen leichteren Alternativen, Suspense und Server Components, das Testen ohne Theater. Zu jeder dieser Stationen gibt es mehr zu sagen, als ein Überblick tragen kann, und genau das ist der Plan: Die Details folgen als eigene Beiträge, jeder mit einem konkreten Beispiel und mindestens einem Fehlerfall. Wer den großen Bogen im Kopf hat, weiß dann, an welcher Stelle er gerade steht.

Weiterführende Quellen

Wie fandest du diesen Beitrag?

Kommentare