Komponentengetriebene Entwicklung kehrt den traditionellen Top-down-Ansatz beim Erstellen von Benutzeroberflächen um. Statt Seiten zu bauen und wiederverwendbare Teile nachträglich herauszulösen, beginnen Sie mit einzelnen Komponenten und setzen sie zu Ansichten zusammen.
Der Gewinn liegt nicht nur in der Wiederverwendung. Wenn eine Komponente für sich allein gebaut und erprobt wird, werden ihre Grenzfälle früh sichtbar, ihre API muss explizit sein, und die Seite, die sie später nutzt, ist größtenteils Montage. Der Preis ist etwas mehr Vorüberlegung zu den Grenzen der Komponenten – und dieser Artikel möchte ihn senken.
Die Komponentenhierarchie
Organisieren Sie Komponenten in einer klaren Hierarchie: Atome (Buttons, Eingabefelder, Beschriftungen), Moleküle (Suchleisten, Formulargruppen), Organismen (Navigation, Hero-Bereiche), Templates (Seitenlayouts) und Seiten (konkrete Ausprägungen). Diese von Brad Frost bekannt gemachte Atomic-Design-Methodik liefert ein gemeinsames Vokabular, um über UI-Architektur zu sprechen.
Behandeln Sie die Hierarchie als Vokabular, nicht als Ordnerstruktur, der Sie gehorchen müssen. Teams verlieren sich oft in der Frage, ob eine Karte ein Molekül oder ein Organismus ist. Für Code ist folgende Aufteilung nützlicher:
- Primitives, die nichts über Ihre Domäne wissen:
Button,Input,Badge,Dialog. - Kompositionen, die Primitives zu einem erkennbaren Muster anordnen:
SearchField,StatCard,EmptyState. - Features, die Kompositionen an echte Daten und echtes Verhalten binden:
InvoiceTable,CheckoutSummary.
Die Regel, die das gesund hält: Abhängigkeiten zeigen nur nach unten. Ein Primitive importiert nie ein Feature, und eine Komposition ruft nie Daten ab.
Isoliert entwickeln
Bauen und testen Sie Komponenten außerhalb des Kontexts einer vollständigen Seite. Werkzeuge wie Storybook oder die Container API von Astro, mit der sich eine Komponente für sich allein rendern lässt, ermöglichen es, Komponenten mit unterschiedlichen Props, Zuständen und Datenvarianten darzustellen. So werden Grenzfälle früh erkannt und die Komponenten robuster.
Isolation zwingt Sie, Fragen zu beantworten, die Sie auf einer vollständigen Seite ignorieren könnten. Wie sieht die Tabelle mit null Zeilen aus? Mit einer Zeile, deren Name 80 Zeichen lang ist? Während des Ladens? Nachdem die Anfrage fehlgeschlagen ist? Halten Sie jeden dieser Fälle als benannten Zustand fest und rendern Sie ihn:
export const states = {
loading: { status: 'loading' },
empty: { status: 'ready', rows: [] },
error: { status: 'error', message: 'Could not load invoices.' },
long: { status: 'ready', rows: [longNameRow, ...sampleRows] },
};
Isolierte Entwicklung macht außerdem visuelle Regressionstests praktikabel. Erstellen Sie Snapshots Ihrer Komponentenzustände und erkennen Sie unbeabsichtigte visuelle Änderungen zwischen Releases automatisch. Halten Sie die Snapshots fokussiert – ein Komponentenzustand pro Bild –, damit ein fehlschlagender Vergleich direkt auf die Ursache zeigt statt auf eine ganze Seite.
Props und Datenfluss
Entwerfen Sie klare Komponenten-APIs mit wohldefinierten Props. Dokumentieren Sie erwartete Eingaben mit TypeScript-Interfaces und legen Sie sinnvolle Standardwerte fest. Bevorzugen Sie bei den meisten Komponenten die explizite Übergabe von Props gegenüber Context oder globalem State – das macht den Datenfluss nachvollziehbar und die Komponenten leichter testbar.
Einige Gewohnheiten sorgen für APIs, die gut altern:
- Zustände modellieren, nicht Booleans.
isLoading,isErrorundisEmptyals getrennte Flags erlauben unmögliche Kombinationen. Eine einzelnestatus-Union nicht. - Varianten nach Absicht benennen.
variant="danger"übersteht ein Rebranding;color="red"nicht. - Children oder Slots für Inhalte akzeptieren. Eine Komponente mit den Props
title,subtitle,iconundfooterTextwird immer weiter wachsen; eine, die zusammengesetzte Inhalte akzeptiert, nicht. - Native Attribute durchreichen. Ein
Buttonsolltetype,disabledundaria-*akzeptieren, ohne dass jedes davon neu deklariert werden muss.
type Status = 'loading' | 'empty' | 'error' | 'ready';
interface StatCardProps {
/** Kurze Beschriftung über dem Wert. */
label: string;
/** Formatierter Wert; wird ignoriert, sofern status nicht 'ready' ist. */
value?: string;
status: Status;
}
Context und globale Stores haben weiterhin ihren Platz: Theme, Sprache, der aktuelle Nutzer. Diese Daten sind tatsächlich allgegenwärtig. Die meisten anderen Daten sollten über Props ankommen, denn eine Komponente, deren Eingaben alle in ihrer Signatur sichtbar sind, lässt sich überall rendern – auch in einem Test.
Komponenten testen
Unit-Tests prüfen, ob Komponenten mit bestimmten Props korrekt rendern und Benutzerinteraktionen wie erwartet verarbeiten. Integrationstests stellen sicher, dass zusammengesetzte Komponenten korrekt zusammenarbeiten. Folgen Sie dem Leitprinzip von Testing Library: Testen Sie Verhalten, nicht Implementierungsdetails.
In der Praxis bedeutet das, nach dem abzufragen, was ein Nutzer wahrnimmt – Rolle, zugänglicher Name, sichtbarer Text –, statt nach Klassennamen oder internem State:
test('shows the error message and a retry button', async () => {
render(InvoiceTable, { status: 'error', message: 'Could not load invoices.' });
expect(screen.getByText('Could not load invoices.')).toBeVisible();
expect(screen.getByRole('button', { name: 'Retry' })).toBeEnabled();
});
Ein so geschriebener Test übersteht ein Refactoring des Markups und dient zugleich als kleiner Barrierefreiheitstest: Wenn Sie den Button nicht über seine Rolle und seinen Namen finden, kann ein Screenreader es auch nicht. Reine Logik, etwa die Funktion, die eine Variante auf Klassennamen abbildet, testen Sie am einfachsten direkt als gewöhnliche Funktion ganz ohne Rendern.
Dokumentieren beim Entwickeln
Isolation liefert Ihnen die Dokumentation fast geschenkt, sofern Sie sie festhalten, solange die Entscheidungen noch frisch sind. Erfassen Sie für jede Komponente:
- Wofür sie gedacht ist und, ebenso wichtig, wofür nicht.
- Jede Prop mit Typ, Standardwert und einer einzeiligen Beschreibung.
- Die unterstützten Zustände und wie jeder davon aussieht.
- Das Tastaturverhalten und alle ARIA-Attribute, die sie für den Verwender verwaltet.
Schreiben Sie die Beschreibungen direkt in die Typdefinitionen, wie im obigen Beispiel StatCardProps, damit Editoren sie beim Überfahren mit der Maus anzeigen und sie sich nicht vom Code entfernen können.
Wo es schiefgeht
Komponentengetriebene Entwicklung hat typische Fehlerbilder, die man benennen sollte:
- Verfrühte Abstraktion. Lösen Sie eine gemeinsame Komponente heraus, sobald es zwei oder drei echte Verwendungen gibt, nicht für eine einzige vorgestellte künftige Verwendung.
- Prop-Explosion. Wenn eine Komponente mehr Boolean-Props als Zeilen Markup hat, teilen Sie sie in kleinere Teile auf.
- Isolation ohne Integration. Jede Komponente kann in ihrer eigenen Story perfekt aussehen, während die Seite trotzdem kaputtgeht. Behalten Sie auch eine kleine Zahl von Tests auf Seitenebene bei.
Fazit
Komponentengetriebene Entwicklung führt zu Oberflächen, die konsistenter, leichter wartbar und schneller zu bauen sind. Beginnen Sie mit den kleinsten Bausteinen, geben Sie jedem eine explizite API und einen vollständigen Satz an Zuständen, und arbeiten Sie sich durch Komposition nach oben.