Eine der zentralen Fertigkeiten in React ist nicht das Schreiben einzelner Komponenten – es ist das Zerlegen einer fertigen UI in eine sinnvolle Komponenten-Hierarchie. Wer das gut macht, baut wartbare, wiederverwendbare und gut testbare Anwendungen. Wer es schlecht macht, landet bei tausendzeiligen Komponenten, die niemand mehr anfassen will. Dieser Artikel führt durch das fünfstufige Vorgehen aus der offiziellen React-Doku und zeigt, woran man eine gute Komponenten-Aufteilung erkennt.
Das Beispiel-Mockup
Stell dir folgende UI vor: eine Produkttabelle mit Suchfeld und einer Checkbox „nur lagernde Artikel anzeigen". Darunter Zeilen mit Produkten, gruppiert nach Kategorie.
┌────────────────────────────────────────┐
│ [Suche...] [☐ Nur lagernd] │
├────────────────────────────────────────┤
│ Frucht │
│ Apfel 1,50 │
│ Birne 2,00 │
│ Gemüse │
│ Karotte 0,80 │
│ Tomate 1,20 │
└────────────────────────────────────────┘Daten kommen als Liste von Produkten:
const products = [
{ category: 'Frucht', name: 'Apfel', price: '1,50', stocked: true },
{ category: 'Frucht', name: 'Birne', price: '2,00', stocked: false },
{ category: 'Gemüse', name: 'Karotte',price: '0,80', stocked: true },
{ category: 'Gemüse', name: 'Tomate', price: '1,20', stocked: true },
];Schritt 1 – Hierarchie zeichnen
Zeichne (im Kopf oder auf Papier) Rahmen um jede Komponente. Eine Komponente sollte idealerweise eine Sache tun. Kriterien zur Abgrenzung:
- Single Responsibility – Wenn eine Box logisch zusammengehört, ist sie ein Kandidat.
- CSS – Was du sowieso als eigene CSS-Klasse stylen würdest, ist oft auch eine Komponente.
- Daten – Bekommt ein Bereich genau ein Objekt als Input, ist das ein guter Schnitt.
Für das Mockup ergibt sich:
FilterableProductTable (Wurzel-Komponente)
├── SearchBar (Suche + Checkbox)
└── ProductTable
├── ProductCategoryRow (Kategorie-Header)
└── ProductRow (eine Produkt-Zeile)Diese Hierarchie ist die spätere Datei- und Import-Struktur.
Schritt 2 – Statische Version bauen
Bevor du an Interaktivität denkst, baue die Komponenten ohne State – nur mit Props. Daten fließen von oben nach unten. Es soll genau das angezeigt werden, was die Daten hergeben.
function ProductTable({ products }) {
const rows = [];
let lastCategory = null;
products.forEach((product) => {
if (product.category !== lastCategory) {
rows.push(
<ProductCategoryRow
category={product.category}
key={product.category}
/>
);
}
rows.push(<ProductRow product={product} key={product.name} />);
lastCategory = product.category;
});
return (
<table>
<thead>
<tr><th>Name</th><th>Preis</th></tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
}Wichtig: In dieser Phase kein useState, kein Event-Handler. Nur Daten ⇒ JSX. Die statische Variante zwingt dich, die Hierarchie und Datenflüsse sauber zu durchdenken.
Schritt 3 – Minimalen State finden
Jetzt kommt die wichtigste Frage: Was ist State?
State ist nur das, was sich über die Zeit ändert und sich nicht aus anderen Daten berechnen lässt. Alles, was abgeleitet werden kann, ist kein State.
Geh die Daten durch und stelle drei Fragen:
- Bleibt der Wert über die Zeit gleich? -> Konstante, kein State.
- Wird er von einem Elternteil als Prop übergeben? -> Prop, kein State.
- Lässt er sich aus anderen Werten berechnen? -> Abgeleitet, kein State.
Bleibt nichts übrig: dann ist es State.
Für unser Beispiel:
| Wert | State? |
|---|---|
| Originale Produktliste | − Prop von außen |
| Eingegebener Suchtext | + State (ändert sich, nicht ableitbar) |
| Checkbox „nur lagernd" | + State (ändert sich, nicht ableitbar) |
| Gefilterte Liste | − Berechnet aus Liste + Suchtext + Checkbox |
Es bleiben also zwei State-Werte übrig. Das ist das Minimum.
Schritt 4 – State auf die richtige Ebene heben
Jetzt steht die nächste Frage: Wo lebt der State?
Vorgehen:
- Welche Komponenten lesen den State?
SearchBar(zeigt Suchtext) undProductTable(filtert damit). - Welche Komponente ändert ihn?
SearchBar(Eingaben). - Suche den niedrigsten gemeinsamen Vorfahren, der beide Komponenten umschließt – das ist
FilterableProductTable.
Dort kommt der State hin:
import { useState } from 'react';
function FilterableProductTable({ products }) {
const [filterText, setFilterText] = useState('');
const [inStockOnly, setInStockOnly] = useState(false);
return (
<div>
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
/>
<ProductTable
products={products}
filterText={filterText}
inStockOnly={inStockOnly}
/>
</div>
);
}Tieferes zum Thema: State Lifting.
Schritt 5 – Datenfluss umkehren
Die Filterwerte werden gelesen, aber noch nicht verändert. Damit die SearchBar den State des Eltern-Components ändern kann, übergibt das Elternteil Setter-Funktionen als Props nach unten – das Kind ruft sie bei Events auf.
<SearchBar
filterText={filterText}
inStockOnly={inStockOnly}
onFilterTextChange={setFilterText}
onInStockOnlyChange={setInStockOnly}
/>function SearchBar({
filterText,
inStockOnly,
onFilterTextChange,
onInStockOnlyChange,
}) {
return (
<form>
<input
type="text"
value={filterText}
placeholder="Suche..."
onChange={(e) => onFilterTextChange(e.target.value)}
/>
<label>
<input
type="checkbox"
checked={inStockOnly}
onChange={(e) => onInStockOnlyChange(e.target.checked)}
/>
Nur lagernde Artikel
</label>
</form>
);
}Damit ist das Grundprinzip von React in einem Bild:
- Props fließen nach unten.
- Events fließen nach oben.
- State lebt so weit oben wie nötig, so weit unten wie möglich.
Heuristiken für gute Komponenten-Aufteilung
- 20–80 Zeilen pro Komponente sind ein gesunder Bereich. Längeres lässt sich meist sinnvoll aufteilen.
- Eine Komponente sollte einen Satz beschreiben können: „Zeigt eine einzelne Produktzeile." Wenn dein Satz „und" enthält, ist es vermutlich zu viel.
- Wiederverwendung ist nicht das oberste Ziel. Manche Komponenten existieren nur einmal – das ist okay, solange sie eine klare Aufgabe haben.
- Nicht zu früh abstrahieren. Erst wenn dasselbe Muster zum dritten Mal auftaucht, lohnt sich eine generische Komponente.
Häufige Anti-Patterns
Den State zu hoch liften.
Wenn nur ein einziges Kind den State braucht, gehört er dort hin – nicht in den Großeltern-Container.
Abgeleitete Werte als State speichern.
Sobald du useEffect schreibst, um eine state aus einem anderen state synchron zu halten, hast du ein Problem. Berechne den Wert einfach beim Rendern.
Riesige App-Komponenten.
Wenn deine App.jsx 600 Zeilen lang ist und überall State und Effekte mischt, fehlt die Hierarchie.
Zu viele Boolean-Props.
<Button isPrimary isLarge isDisabled isLoading isRound /> – das schreit nach Variant-Props oder einer Aufteilung.
Weiterführende Ressourcen
Externe Quellen
- Thinking in React – react.dev
- Choosing the State Structure – react.dev
- Sharing State Between Components – react.dev