Von DOM-Skripten bis hin zur UI als Funktion des Zustands: Das Kernmodell von React
Warum React manuelle DOM-Änderungen durch deklarative Komponenten ersetzt und wie JSX, Props, State, Neuerstellung der Darstellung sowie ein-einwegiger Datenfluss in ein einheitliches Konzept passen.
Eine Like-Taste, die einen Zähler aktualisiert, ein Icon austauscht und eine Animation abspielt, lässt sich mithilfe einfacher DOM-Aufrufe leicht erstellen. Fünfzig solcher Funktionen in einem Live-Feed, wobei jede auch Kommentare, Teile sowie den gespeicherten Zustand verfolgt, führen dazu, dass handgeschriebener DOM-Code an seine Grenzen stößt. React existiert, um genau diese Art von Problemen zu beseitigen: Man beschreibt, wie das Bildschirmbild für die aktuellen Daten aussehen soll, und React ermittelt anschließend, welche DOM-Änderungen notwendig sind.
Dieser Leitfaden führt durch die Konzepte, die das möglich machen, in der Reihenfolge, in der sie aufeinander aufbauen: JSX, Komponenten, Props, Zustand, Neurendern, der deklarative Stil sowie die Art und Weise, wie Daten und Ereignisse durch einen Komponentenbaum fließen. Am Ende sollten Sie in der Lage sein, vorherzusagen, wann eine Komponente neu gerendert wird, zu entscheiden, wohin ein Zustandswert gehört, und die Anfängerfehler zu erkennen, die Updates stören.
Das Problem, das React lösen soll
Bevor Komponentenbibliotheken zur Norm wurden, bedeutete eine interaktive Benutzeroberfläche imperativen Code: Man musste die Elemente finden und dann bei jedem Ereignis selbst alle Änderungen vornehmen. Für einen einzelnen „Gefällt mir“-Button sieht die Einrichtung so aus:
// Traditional DOM manipulation
const button = document.getElementById("like-button");
const countEl = document.getElementById("like-count");
const icon = document.getElementById("like-icon");
let liked = false;
let count = 42;
und der Klick-Handler muss sich an alle visuellen Details beider Zustände erinnern:
button.addEventListener("click", function() {
if (!liked) {
liked = true;
count++;
countEl.textContent = count;
button.classList.add("liked");
button.classList.remove("unliked");
icon.src = "/icons/heart-filled.svg";
button.style.color = "#e0245e";
// trigger animation
button.classList.add("animate-pop");
setTimeout(() => button.classList.remove("animate-pop"), 300);
} else {
liked = false;
count--;
countEl.textContent = count;
button.classList.remove("liked");
button.classList.add("unliked");
icon.src = "/icons/heart-empty.svg";
button.style.color = "#6c757d";
}
});
Das ist bei einem einzigen Button noch handhabbar. Stellen Sie sich nun einen Feed mit 50 Beiträgen vor, jeder mit eigenen Steuerelementen für „Gefällt mir“, Kommentare, Teilen und Speichern – all diese können aufgrund von Benutzerklicks oder neu eingehender Daten vom Server geändert werden. Der Code verwandelt sich in ein Netz aus Elementsuchen, Listern und Variablen, die manuell synchronisiert werden müssen. Mit wachsender Anwendung treten drei Fehlermöglichkeiten auf:
- Synchronisation. Der DOM zeigt etwas anderes an als die Variablen. Wenn Sie das Zählelement aktualisieren, aber die Variable vergessen – oder umgekehrt – müssen Sie nun zwei mögliche Quellen der Wahrheit zur Fehlersuche berücksichtigen.
- Wiederverwendbarkeit. Der Handler ist an bestimmte Element-IDs sowie eine spezifische HTML-Struktur gebunden, wodurch ein Verschieben der Schaltfläche auf eine andere Seite deren Neuimplementierung erfordert.
- Komplexität. Jede neue Funktion zwingt Sie dazu, alles zu verstehen, was davon beeinflusst werden könnte. Kleine Änderungen können entfernte Teile der Seite stören.
React, das 2013 von Facebook als Open-Source-Projekt veröffentlicht wurde, löst all diese Probleme mit einer Idee: Anstatt weiterhin DOM-Befehle auszusenden, beschreiben Sie einfach die Benutzeroberfläche, die für einen bestimmten Zustand existieren sollte. React vergleicht diese Beschreibung mit dem, was auf dem Bildschirm angezeigt wird, und korrigiert die Unterschiede. Sie beschreiben – React aktualisiert.
JSX: Markup, das in JavaScript kompiliert wird
Das Erste, was bei React-Code ungewöhnlich erscheint, sind HTML-ähnliche Markups innerhalb einer Funktion:
function Greeting() {
return (
<div className="greeting">
<h1>Hello, Priya!</h1>
<p>Welcome back.</p>
</div>
);
}
Dieses Markup ist JSX – eine Syntaxerweiterung, die es ermöglicht, Elementbäume in JavaScript-Dateien zu schreiben. Es handelt sich weder um HTML noch um etwas, was der Browser versteht. In einem Kompilierschritt (Babel, der TypeScript-Compiler oder ein Bundler, der diese Tools verwendet) wird es in gewöhnliche Funktionsaufrufe umgeschrieben, bevor der Code überhaupt ausgeführt wird. Man schreibt:
// What you write (JSX):
return (
<h1 className="title">Hello!</h1>
);
und der Compiler erzeugt etwas Äquivalentes zu:
// What the compiler transforms it into:
return React.createElement("h1", { className: "title" }, "Hello!");
React.createElement gibt lediglich ein einfaches Objekt zurück, das das Element beschreibt: seinen Typ, die Eigenschaften und die Kinderelemente. React liest diese Objekte, um zu entscheiden, was im eigentlichen DOM enthalten sein soll. (Neuere JSX-Transformationsfunktionen verwenden stattdessen einen anderen Hilfsfunktion aus react/jsx-runtime, doch das Ergebnis ist weiterhin ein ähnliches Beschreibungsobjekt.) Niemand schreibt diese Aufrufe manuell; JSX existiert, weil verschachtelte Markup-Strukturen viel leichter lesbar sind.
Wo JSX sich von HTML unterscheidet
Die Syntax ähnelt HTML, ist aber nicht identisch. Die Unterschiede, die die meisten Menschen in Verwirrung bringen:
// HTML JSX
// ───────────────────────────── ──────────────────────────────────
// class="title" className="title" ← JS reserved word
// for="email" htmlFor="email" ← JS reserved word
// <input> (self-closing optional) <input /> ← must self-close
// onclick="handler()" onClick={handler} ← camelCase, no quotes
// style="color: red" style={{ color: "red" }} ← JS object
class und for sind Reservierungs-Wörter in JavaScript, weshalb JSX className und htmlFor verwendet. Jedes Element muss geschlossen werden. Event-Handler sind im camelCase-Format und erhalten eine Funktion, nicht einen String. Inline-Stile sind Objekte. Für ein tieferes Verständnis der Vor- und Nachteile dieser Syntax siehe warum JSX kein HTML ist.
Einfügen von JavaScript-Expressions
Klammern öffnen ein Fenster zu JavaScript. Alles, was einen Wert ergibt, kann darin verwendet werden: Variablen, Funktionsaufrufe, Ternäre Ausdrücke, Array-Methoden. In dieser Karte wird zunächst der Online-Status berechnet:
function UserCard({ user }) {
const isOnline = user.lastSeen < Date.now() - 5 * 60 * 1000;
danach formen mehrere Expressions den Markup-Teil: eine gekürzte Biografie, ein bedingter Klassenname, ein bedingtes Etikett sowie ein formatierter Datum:
return (
<div className="card">
<img src={user.avatar} alt={user.name} />
<h2>{user.name}</h2>
<p>{user.bio.length > 100 ? user.bio.slice(0, 100) + "..." : user.bio}</p>
<span className={isOnline ? "badge-green" : "badge-grey"}>
{isOnline ? "Online" : "Offline"}
</span>
<p>Joined: {new Date(user.joinedAt).toLocaleDateString()}</p>
</div>
);
}
Die Regel ist einfach: Klammern enthalten Ausdrücke, nicht Anweisungen. Man kann direkt innerhalb der Markup-Struktur einen ternären Operator verwenden, aber keinen if-Block, sowie .map(), aber keinen for-Loop.
Komponenten: Funktionen, die UI zurückgeben
Eine Komponente ist ein wiederverwendbarer, selbstständiger UI-Anteil, der als JavaScript-Funktion geschrieben wird und JSX zurückgibt. Das ist die gesamte Definition:
// A component is just a function that returns JSX
function Button() {
return (
<button className="btn">
Click me
</button>
);
}
Man verwendet sie genauso wie eine HTML-Tag und kann so viele davon einsetzen, wie man möchte:
function App() {
return (
<div>
<Button />
<Button />
<Button />
</div>
);
}
Dies rendernt drei identische Schaltflächen aus einer einzigen Definition.
Kleine Komponenten zu größeren zusammenstellen
Komponenten werden mächtig, wenn sie verschachtelt sind. Kleine, einzigartige Bausteine werden zu größeren zusammengefügt, ähnlich wie Bauklötze. Ein Avatar weiß nur, wie er ein Bild anzeigt:
function Avatar({ src, alt }) {
return <img className="avatar" src={src} alt={alt} />;
}
Und darauf aufbauend entsteht eine Kette aus etwas größeren Komponenten: ein Name-Block, eine Benutzerinformationsspalte, die Avatar und Namen kombiniert, sowie eine Postkarte, die Benutzerinformationen mit dem Postinhalt verbindet:
function UserName({ name, handle }) {
return (
<div>
<strong>{name}</strong>
<span>@{handle}</span>
</div>
);
}function UserInfo({ user }) {
return (
<div className="user-info">
<Avatar src={user.avatar} alt={user.name} />
<UserName name={user.name} handle={user.handle} />
</div>
);
}function PostCard({ post, author }) {
return (
<article className="post-card">
<UserInfo user={author} />
<p>{post.content}</p>
<span>{post.likes} likes</span>
</article>
);
}
Jede Schicht hat eine einzige Aufgabe. Avatar zeigt ein Bild an, UserInfo platziert das Avatar neben den Namen und PostCard strukturiert einen ganzen Beitrag. Das ist die Vorgehensweise, die React fördert: Die Benutzeroberfläche in die kleinstmöglichen, sinnvollen Teile aufteilen, jedem Teil eine klare Verantwortung zuweisen und diese anschließend kombinieren. Der Leitfaden zur Erstellung wiederverwendbarer React-Komponenten geht weiter auf das Komponentendesign ein.
Warum Komponentennamen großgeschrieben werden
React unterscheidet zwischen nativen Elementen und Komponenten anhand des ersten Buchstabens. <button> wird zu einem DOM-Button; <Button> veranlasst React, in dem Gültigkeitsbereich nach einer Variablen mit dem Namen Button zu suchen und diese aufzurufen. Eine mit einem Kleinbuchstaben benannte Komponente wird stillschweigend als unbekanntes HTML-Tag behandelt, was eine häufige Ursache für die Verwirrung „Meine Komponente zeigt nichts an“ ist.
Props: Eine Komponente von außen konfigurieren
Ein Button, der stets „Klick mich“ anzeigt, ist nicht sehr nützlich. Props (Abkürzung für Properties) sind die Methode, mit der ein Elternteil Daten an ein Kind weitergibt, sodass eine einzige Definition für viele Situationen geeignet ist.
Props fließen in eine Richtung – vom Komponenten, der eine andere komponiert, hin zur komponierten Komponente – und kommen als ein einzelnes Objektargument an. Der Elternteil gibt sie wie Attribute an:
// Parent passes data as props (looks like HTML attributes)
function App() {
return (
<div>
<Button label="Submit" colour="blue" />
<Button label="Cancel" colour="grey" />
<Button label="Delete" colour="red" />
</div>
);
}
Und das Kind destrukturiert, was es benötigt:
// Child receives them as an object
function Button({ label, colour }) {
return (
<button className={`btn btn-${colour}`}>
{label}
</button>
);
}
Eine Definition, drei Buttons mit unterschiedlichem Aussehen, da jeder unterschiedliche Werte erhalten hat.
Props können jeden Wert enthalten
Schlüsselwörter sind nur der Anfang. Zahlen, Boolesche Werte, Arrays, Objekte und Funktionen sind alle gültige Props. Eine Produktkarte kann beispielsweise ein Datenobjekt, eine Callback-Funktion und einen Flag-Wert enthalten:
function ProductCard({ product, onAddToCart, featured }) {
return (
<div className={`card ${featured ? "card-featured" : ""}`}>
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>₹{product.price.toLocaleString()}</p>
<p>{product.rating} ★ ({product.reviewCount} reviews)</p>
<button onClick={onAddToCart}>
Add to Cart
</button>
</div>
);
}
Am Aufrufort werden diese Werte in Klammern übergeben:
// Usage:
<ProductCard
product={{ name: "Headphones", price: 2499, rating: 4.3, reviewCount: 128 }}
onAddToCart={() => addToCart(product.id)}
featured={true}
/>
Beachten Sie, dass die inline Callback-Funktion onAddToCart auf product.id verweist, doch product ist hier lediglich das als Eigenschaft übergebene Objektliteral und keine Variable im Scope. Im echten Code würde man eine in dem Elternteil definierte Variable verwenden, zum Beispiel onAddToCart={() => addToCart(item.id)}. Die Übermittlung von Funktionen als Eigenschaften ist die übliche Methode, damit Kinder Ereignisse melden können – was weiter unten erneut vorkommt.
Eigenschaften sind nur zum Lesen
Eine Komponente darf ihre eigenen Eigenschaften niemals ändern. Während einer Renderung sind sie feste Eingabewerte. Wenn sich etwas ändern muss, ändert das Elternteil es und rendernt das Kind mit dem neuen Wert. Die Neuzuweisung einer Eigenschaft innerhalb des Kindes ist ein Fehler:
// WRONG — never modify props
function Button({ count }) {
count = count + 1; // ← this is a mistake
return <button>{count}</button>;
}
Die Neuzuweisung ändert nur eine lokale Variable; sie erreicht niemals die übergeordnete Ebene und verschwindet bei der nächsten Darstellung. Wenn eine Komponente einen Wert benötigt, der sich ändern kann, dient genau das dem State:
// CORRECT — props are read-only, use state for data that changes
function Button({ initialCount }) {
const [count, setCount] = useState(initialCount);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
Hier initialisiert initialCount lediglich den State; nach der ersten Darstellung besitzt die Komponente ihre eigene Zählung.
State: das eigene Speicher der Komponente
Props stammen von außen. State sind Daten, die der Komponente gehören und sich im Laufe der Zeit ändern können. Wenn sich der State ändert, rendernt React die Komponente erneut mit dem neuen Wert – man greift dabei niemals selbst auf das DOM zu.
State wird mit dem useState-Hook erstellt, der aus React importiert wird:
import { useState } from "react";
Ein Zähler zeigt die grundlegende Struktur dar:
function Counter() {
const [count, setCount] = useState(0);
// ↑ current value ↑ function to update it ↑ initial value return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setCount(count - 1)}>Decrement</button>
<button onClick={() => setCount(0)}>Reset</button>
</div>
);
}
useState(0) gibt ein Paar zurück: den aktuellen Wert (count, der bei 0 beginnt) sowie einen Setter (setCount). Durch Aufruf des Setters mit einem neuen Wert weist man React an, diesen zu speichern und das Komponenten-Rendering neu durchzuführen.
Aufbau des „Like“-Buttons mit State
Der imperative „Like“-Button aus dem Anfang wird dadurch deutlich kleiner. Beginnen Sie mit der Import-Anweisung:
import { useState } from "react";
und beschreiben Sie anschließend den Button für jede Kombination aus liked und count:
function LikeButton({ initialLikes }) {
const [liked, setLiked] = useState(false);
const [count, setCount] = useState(initialLikes); function handleClick() {
if (liked) {
setLiked(false);
setCount(c => c - 1);
} else {
setLiked(true);
setCount(c => c + 1);
}
} return (
<button
onClick={handleClick}
className={liked ? "btn-liked" : "btn-default"}
>
{liked ? "♥" : "♡"} {count}
</button>
);
}
Es gibt keine Abfragen nach Elementen, keine Aufrufe von classList und keine Zuweisungen an textContent. Der Handler aktualisiert zwei State-Werte, und der JSX-Code legt fest, wie der Button für diese Werte aussieht. React sorgt anschließend dafür, dass das DOM dem entspricht.
Achten Sie auf das Aktualisierungsformular setCount(c => c - 1). Durch die Übermittlung einer Funktion anstelle eines Wertes erhalten Sie den neuesten Zustand, was sicherer ist, wenn mehrere Aktualisierungen im selben Ereignis in der Warteschlange sind.
Jede Instanz behält ihren eigenen Zustand bei
Der Zustand gehört zu einer bestimmten Instanz eines Komponenten, nicht zur Definition der Komponente. Zeichnen Sie drei solche Schaltflächen – jede hat ihren eigenen liked- und count-Wert; das Klicken auf eine beeinflusst die anderen nicht:
function Feed() {
return (
<div>
<LikeButton initialLikes={24} /> {/* has its own state */}
<LikeButton initialLikes={7} /> {/* has its own state */}
<LikeButton initialLikes={156} /> {/* has its own state */}
</div>
);
}
Was gehört in den Zustand?
Verwenden Sie den Zustand für Werte, die:
- im Laufe der Zeit aufgrund von Interaktionen oder eingehenden Daten ändern
- die Benutzeroberfläche bei ihrer Änderung aktualisieren sollten
- zu dieser spezifischen Komponenteninstanz gehören
Vermeiden Sie es, Zustand zu speichern, den Sie aus vorhandenen Eigenschaften oder Zuständen berechnen können (berechnen Sie ihn stattdessen während des Renderings), sowie Werte, die sich ändern, ohne dass ein Neurendering erforderlich ist – wie beispielsweise eine Timer-ID, die besser in einen ref passt.
Wie das Neurendering funktioniert
Das Neurendering ist der Motor hinter dem gesamten Modell. Wenn sich der Zustand ändert, ruft React Ihre Komponentenfunktion erneut auf, erhält eine neue Beschreibung der Benutzeroberfläche, vergleicht sie mit der vorherigen und aktualisiert nur die Teile des DOMs, die sich geändert haben.
Beim ersten Render erzeugt der Zähler seine Markup-Struktur und React erstellt die DOM-Node:
Initial render:
count = 0
Component runs → returns <p>Count: 0</p> <button>Increment</button>
React creates DOM nodes
Wenn der Benutzer klickt, löst der Setter ein neues Rendering aus und React vergleicht die beiden Beschreibungen:
User clicks Increment:
setCount(1) called
React re-renders the component
count = 1
Component runs again → returns <p>Count: 1</p> <button>Increment</button>
React compares: <p>Count: 0</p> vs <p>Count: 1</p>
React updates only the text node inside <p>
Button is unchanged — React leaves it alone
Nur der Text innerhalb des Absatzes ändert sich im echten DOM. Die Schaltfläche bleibt unberührt. React baut die Seite nicht neu auf; es berechnet den kleinsten Satz an Änderungen und wendet nur diese an, weshalb häufige Neuberechnungen in der Regel kein Problem darstellen.
Was veranlasst ein Komponente zur Neuberechnung?
Eine Komponente wird erneut berechnet, wenn sich ihr eigener Zustand ändert:
1. The component's own state changes (setCount, setUser, etc.)
↓
Component re-renders
Sie wird auch in einigen anderen Situationen erneut berechnet:
2. The component's props change (parent passes different values)
↓
Component re-renders3. A context the component uses changes
↓
Component re-renders4. The parent re-renders
↓
All children re-render (unless memoized)
In der Praxis sind „Props haben sich geändert“ und „Elternteil wurde neu berechnet“ dasselbe Ereignis aus zwei Perspektiven: Ein Kind erhält neue Props nur, weil sein Elternteil erneut berechnet wurde. Die praktische Folge ist, dass standardmäßig eine Neuberechnung eines Elternteils auf alle seine Kinder übertragen wird, unabhängig davon, ob sich ihre Props geändert haben oder nicht – es sei denn, sie sind gememorisert.
Diese Abfolge von Renderings stellt selten ein Problem dar. Ein Rendering ist lediglich ein Funktionsaufruf, der Objekte erzeugt; der aufwändige Teil besteht darin, den echten DOM zu verändern, was React auf ein Minimum beschränkt. Die meisten tatsächlichen Leistungsprobleme entstehen durch die Strukturierung von Komponenten und Zustand, nicht durch die reine Anzahl der Renderings.
Der virtuelle DOM und die Abgleichung
React führt eine leichte JavaScript-Repräsentation der Benutzeroberfläche, oft als virtueller DOM bezeichnet. Jedes Rendering erzeugt einen neuen Baum aus Elementobjekten, und React vergleicht diesen mit dem vorherigen Baum in einem Prozess, der als Abgleichung bezeichnet wird. Das Ergebnis dieses Vergleichs ist die Liste der tatsächlichen DOM-Operationen, die ausgeführt werden müssen.
Man arbeitet niemals direkt mit dieser Schicht; es handelt sich um ein Implementierungsdetail. Das ist wichtig, weil der Vergleich einfacher Objekte im Speicher kostengünstig ist, während echte DOM-Operationen im Vergleich dazu langsam sind. Durch den Wegleiten jeder Aktualisierung über diesen Vergleich kann React die aufwändigen Aufgaben in Batches ausführen und so minimieren.
Deklarative versus imperative UI
React ist deklarativ – und das Verständnis dieses Begriffs bedeutet den eigentlichen Denkwechsel. Vergleichen Sie zwei Arten, eine Liste anzuzeigen.
Imperativ: Auflistung der Schritte
Die imperative Version leert zunächst den Container:
// Imperative: manual DOM manipulation
const list = document.getElementById("list");
list.innerHTML = ""; // clear it
dann erstellt, konfiguriert und fügt jedes Element manuell hinzu:
items.forEach(item => {
const li = document.createElement("li");
li.textContent = item.name;
li.className = item.active ? "active" : "";
li.addEventListener("click", () => handleClick(item.id));
list.appendChild(li);
});
Der Code besteht aus einer Abfolge von Befehlen: Erstelle diesen Knoten, setze diese Eigenschaft, füge diesen Zuhörer hinzu, füge ihn dort ein. Man ist für das Vorher, das Nachher und jeden Schritt dazwischen verantwortlich.
Deklarativ: Beschreibung des Ergebnisses
Die deklarative Version beschreibt, wie die Liste für jedes items-Array aussehen sollte:
// Declarative: describe the desired output
function ItemList({ items, onItemClick }) {
return (
<ul>
{items.map(item => (
<li
key={item.id}
className={item.active ? "active" : ""}
onClick={() => onItemClick(item.id)}
>
{item.name}
</li>
))}
</ul>
);
}
Es gibt kein „Die Liste leeren und anschließend neu erstellen“. Man beschreibt das Ziel, und React ermittelt selbst, wie man vom aktuellen DOM zu diesem Ziel gelangt. Die key-Eigenschaft teilt React mit, welches Element bei jeder Neuzeichnung welches ist, sodass es die richtigen Elemente verschieben, aktualisieren oder entfernen kann, anstatt alle neu zu erstellen.
Warum der deklarative Stil für UIs geeignet ist
- Vorhersehbarkeit. Mit denselben Eigenschaften und Zuständen liefert ein Komponente immer dasselbe Ergebnis. Die Benutzeroberfläche kann als reine Funktion der Daten betrachtet werden:
UI = f(state, props). - Kleineres Gedächtnisbelastung. Man muss nicht nachverfolgen, wie das DOM derzeit aussieht oder welche Änderungen notwendig sind. Man beschreibt lediglich den aktuellen Zustand.
Der Komponentenbaum und der einwegige Datenfluss
Jede React-Anwendung ist ein Baum. Eine Wurzelkomponente, meist App, rendernt Kinderkomponenten, die wiederum ihre eigenen Kinder rendern – dies spiegelt die Struktur des Bildschirms wider:
App
├── Navbar
│ ├── Logo
│ ├── NavLinks
│ └── UserMenu
│ ├── Avatar
│ └── DropdownMenu
├── Dashboard
│ ├── Sidebar
│ │ ├── SidebarLink (×5)
│ │ └── UserStats
│ └── MainContent
│ ├── StatsRow
│ │ ├── StatCard (×4)
│ └── RecentActivity
│ ├── ActivityItem (×10)
└── Footer
Daten fließen nach unten
Daten werden über Props von der Elternkomponente zur Kinderkomponente übertragen. Eine Komponente kann nicht auf den Zustand einer Geschwisterkomponente oder der Elternkomponente zugreifen. Genau dieser einwegige Fluss sorgt dafür, dass große Anwendungen verständlich bleiben:
App (has user, notifications, theme)
│
├── Navbar (receives: user, notifications)
│ │
│ └── UserMenu (receives: user)
│ │
│ └── Avatar (receives: user.avatar, user.name)
│
└── Dashboard (receives: user, theme)
│
└── MainContent (receives: theme)
Avatar sieht nur das, was UserMenu ihm zur Verfügung stellt, und UserMenu sieht nur das, was Navbar ihm zur Verfügung stellt. Nichts dringt seitlich oder nach oben durch, sodass man bei einem falschen Wert die Kette der Elternkomponenten überprüfen muss.
Ereignisse fließen nach oben
Eine Komponente tief im Baum kann den Zustand ihres Elters nicht direkt ändern, kann aber eine von dem Elternteil bereitgestellte Funktion aufrufen. Der Elternteil besitzt den Zustand:
// Parent owns the state and passes down both the value and the updater
function App() {
const [searchQuery, setSearchQuery] = useState("");
und gibt sowohl den Wert als auch den Setter an seine Kinder weiter; die Suchleiste ruft den Setter jedes Mal auf, wenn sich der Eingabewert ändert:
return (
<div>
<SearchBar
query={searchQuery}
onChange={setSearchQuery} {/* passes the setter as a prop */}
/>
<Results query={searchQuery} />
</div>
);
}// Child receives the updater and calls it on user input
function SearchBar({ query, onChange }) {
return (
<input
value={query}
onChange={e => onChange(e.target.value)} {/* calls parent's setter */}
placeholder="Search..."
/>
);
}
Die Abfrage existiert nur in App. SearchBar speichert nichts; es meldet jede Tastenbetätigung über die onChange-Eigenschaft. Results erhält die aktualisierte Abfrage als Eigenschaft und rendert neu. Daten fließen als Eigenschaften nach unten, Ereignisse kommen als Funktionsaufrufe nach oben. (Die erklärenden Kommentare innerhalb der Einrückungstags dienen nur zum Lesen; in echtem JSX gehört ein kommentierter Block zu den Kindern, und innerhalb eines Tags würde man einen einfachen /* */-Kommentar verwenden oder ihn weglassen.)
Fehler, die React-Updates stören
State direkt verändern
Es ist verlockend, ein State-Objekt direkt zu modifizieren und es zurückzugeben:
// WRONG — mutating state directly
const [user, setUser] = useState({ name: "Priya", age: 28 });
Der erste birthday-Beispiel unten ändert das Objekt und gibt dem Setter denselben Referenzwert weiter, wodurch sich die Benutzeroberfläche nicht aktualisiert. Das zweite Beispiel erstellt mit dem Spread-Operator ein neues Objekt, was React als Änderung wahrnimmt:
function birthday() {
user.age = 29; // ← directly modifying the object
setUser(user); // ← same object reference — React sees no change
} // UI does NOT update// CORRECT — create a new object
function birthday() {
setUser({ ...user, age: user.age + 1 }); // ← new object, React detects change
}
React entscheidet, ob sich der Zustand geändert hat, indem es Referenzen mit Object.is vergleicht und nicht indem es den Inhalt prüft. Wenn ein Objekt oder Array vor Ort geändert wird und dieselbe Referenz zurückgegeben wird, kommt React zu dem Schluss, dass sich nichts geändert hat, und kann den Render überspringen.
Auch Arrays folgen dieser Regel. Das Hinzufügen von Elementen in das vorhandene Array schlägt fehl:
// WRONG — mutating the array
const [items, setItems] = useState([1, 2, 3]);
items.push(4); // ← modifies in place
setItems(items); // ← same reference — no re-render
während das Erweitern in ein neues Array funktioniert:
// CORRECT — create a new array
setItems([...items, 4]); // ← spread creates a new array
Misverständnis zwischen Props und Zustand
Ein schneller Test hilft dabei, zu erkennen, was was ist. Kommt der Wert von einem Elternelement? Dann handelt es sich um eine Prop, die nur zum Lesen zugänglich ist. Besitzt das Komponente den Wert und ändert ihn im Laufe der Zeit? Dann handelt es sich um Zustand. Wenn ein niemals ändernder Wert in useState abgelegt wird, ist das ein Zeichen für Verwirrung:
// Wrong: using state for something that should be a prop
function UserCard() {
const [userName] = useState("Priya"); // ← why is this state? It never changes here
return <p>{userName}</p>;
}
Falls das Elternelement die Daten steuert, sollten diese als Prop übergeben werden:
// Correct: static data the parent controls comes as a prop
function UserCard({ name }) {
return <p>{name}</p>;
}
Speichern von Werten, die berechnet werden könnten
Nicht alles, was sich ändert, benötigt einen eigenen Zustand. Das Mitführen einer Zählung neben der Liste, die gezählt wird, führt zu doppelten Informationen:
// Wrong: storing derived data in state
const [items, setItems] = useState([...]);
const [itemCount, setItemCount] = useState(0); // ← why is this state?
Jede Änderung an items erfordert nun eine entsprechende Aktualisierung von itemCount, und eine vergessene Aktualisierung führt dazu, dass beide nicht mehr übereinstimmen. Die Berechnung während der Darstellung hält die beiden von vornherein synchron:
// Every time items changes, you have to remember to also update itemCount
// And if you forget once, they're out of sync// Correct: derive it during render
const [items, setItems] = useState([...]);
const itemCount = items.length; // ← just a variable — always in sync
Komponenten, die alles erledigen
Eine einzige Komponente, die eine Sidebar, eine Tabelle, ein Formular und mehrere Modale darstellt, ist schwer zu lesen, zu testen und wiederverwendbar. Eine nützliche Heuristik: Wenn man nicht in einem kurzen Satz beschreiben kann, was eine Komponente tut, erledigt sie zu viel.
// Hard to maintain — does everything
function UserDashboard() {
// manages user state
// fetches orders
// handles filters
// manages pagination
// controls modal open/close
// renders sidebar
// renders order table
// renders filter controls
// renders pagination
// renders modal
return ( /* 200 lines of JSX */ );
}
Durch Aufteilung nach Verantwortlichkeiten erhält jeder Teil einen Namen und eine Aufgabe:
// Better — clear single responsibilities
function UserDashboard() {
return (
<DashboardLayout>
<OrderFilters />
<OrderTable />
<OrderPagination />
<OrderDetailModal />
</DashboardLayout>
);
}
Entwurf einer App aus Komponenten
Ziehen Sie die Grenzen vor dem Schreiben des Codes
Beginnen Sie mit dem Design und markieren Sie die Komponenten. Alles, was sich wiederholt, ist eine Komponente. Alles mit einer klaren Aufgabe ist eine Komponente. Dinge, die gemeinsam ändern, gehören zusammen; Dinge, die unabhängig voneinander ändern, sollten getrennt werden. Für eine Produktübersichtsseite besteht das Design aus:
Looking at the design:
[ Filter Bar ]
[ Product Card ][ Product Card ][ Product Card ]
[ Product Card ][ Product Card ][ Product Card ]
[ Pagination ]
in diesem Baum auf:
Components:
ProductPage
├── FilterBar
│ ├── FilterGroup (×3)
│ └── SortDropdown
├── ProductGrid
│ └── ProductCard (×N)
└── Pagination
Platzieren Sie den Zustand beim niedrigsten gemeinsamen Elternteil
Für jeden Zustand finden Sie die unterste Komponente im Baum, die ihn benötigt, und lagern den Zustand dort ab:
If only ProductCard needs the "expanded" state → put it in ProductCard
If FilterBar and ProductGrid both need filters → put filters in ProductPage
If Pagination and ProductGrid both need currentPage → put it in ProductPage
Wenn zwei miteinander verbundene Komponenten dieselben Daten benötigen, verschieben Sie den Zustand in ihren nächsten gemeinsamen Elternteil und leiten ihn weiter. Dies wird als „Lifting of State“ bezeichnet. Der Zustand so tief wie möglich zu halten, begrenzt außerdem das Ausmaß der erneuten Darstellung des Baums bei Änderungen.
Bauen Sie zunächst eine statische Version und fügen Sie anschließend den Zustand hinzu
Fangen Sie mit fest codierten Daten an: kein useState, keine Handler, nur Markup, das durch Props gesteuert wird. Sobald die Struktur feststeht, ermitteln Sie, welche Werte im Laufe der Zeit tatsächlich ändern, und führen für genau diese einen Zustand ein. Der erste Schritt ist eine vollständig statische Karte:
// Step 1: static — no state, hardcoded data
function ProductCard() {
return (
<div className="card">
<img src="/headphones.jpg" alt="Headphones" />
<h3>Wireless Headphones</h3>
<p>₹2,499</p>
<button>Add to Cart</button>
</div>
);
}
Im zweiten Schritt wird der fest codierte Inhalt durch ein product-Prop ersetzt, und im dritten Schritt wird ein kleiner Zustand für die „Hinzugefügt“-Bestätigung hinzugefügt, der sich nach zwei Sekunden selbst zurücksetzt:
// Step 2: accept props — still no state
function ProductCard({ product }) {
return (
<div className="card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>₹{product.price.toLocaleString()}</p>
<button>Add to Cart</button>
</div>
);
}// Step 3: add state for interactivity
function ProductCard({ product, onAddToCart }) {
const [added, setAdded] = useState(false); function handleAdd() {
setAdded(true);
onAddToCart(product.id);
setTimeout(() => setAdded(false), 2000);
} return (
<div className="card">
<img src={product.image} alt={product.name} />
<h3>{product.name}</h3>
<p>₹{product.price.toLocaleString()}</p>
<button onClick={handleAdd} disabled={added}>
{added ? "Added ✓" : "Add to Cart"}
</button>
</div>
);
}
Weil Struktur und Interaktivität getrennt sind, lässt sich jeder Schritt unabhängig leicht überprüfen.
Wichtige Erkenntnisse
Das gesamte Modell in Kürze. Warum React existiert:
Why React:
Plain JS DOM manipulation is hard to scale and maintain
React: describe the UI, let React handle DOM updates
und die wesentlichen Aspekte jedes oben besprochenen Konzepts, von JSX bis hin zu häufigen Fehlern:
JSX:
HTML-like syntax in JavaScript — compiled to React.createElement()
{} embeds any JavaScript expression
Use className (not class), onClick (not onclick)Components:
Functions that return JSX
Capital letter names (Button, not button)
Reusable, composable building blocksProps:
Data passed from parent to child (like function arguments)
Read-only — a component never modifies its own props
Can be strings, numbers, objects, arrays, functionsState:
const [value, setValue] = useState(initialValue)
Data owned by a component that changes over time
Calling the setter triggers a re-render
Each component instance has its own stateRe-rendering:
Happens when state changes, props change, or parent re-renders
React diffs the virtual DOM and updates only what changed
Not expensive — surgical DOM updatesDeclarative:
Describe what the UI should look like
React figures out what changed and how to update the DOM
UI = f(state, props) — predictable, testableData flow:
Props flow down (parent → child)
Events flow up (child calls parent's function)
One-way data flow keeps the application predictableCommon mistakes:
Mutating state directly (use spread / new objects)
Storing derived values in state (compute them during render)
Overloading one component (split by responsibility)
- Komponenten sind Funktionen, Props sind ihre Argumente und der Zustand ist ihr Speicher. Die Benutzeroberfläche ist stets eine Projektion des aktuellen Zustands.
- Das Neuerstellen der Darstellung ist durch die Architektur günstig; die Arbeit am DOM, die React erspart, ist der teure Teil. Strukturieren Sie den Zustand sorgfältig, bevor Sie sich um die Anzahl der Neuerstellungen kümmern.
- Geben Sie den Settern immer neue Objekte und Arrays; React vergleicht Referenzen, nicht Inhalte.
- Bewahren Sie den Zustand in der untersten Komponente auf, die ihn benötigt, leiten Sie alles Weitere während der Darstellung ab und lassen Sie Ereignisse über Callback-Props nach oben fließen.
Context, Effects, benutzerdefinierte Hooks sowie Leistungsoptimierungen basieren alle auf diesen Prinzipien. Wenn Sie Komponenten, Props, Zustand und das Neuerstellen der Darstellung gut beherrschen, werden die fortgeschrittenen Aspekte von React zu Erweiterungen eines bereits verstandenen Modells statt zu neuen Regeln, die man auswendig lernen muss.
Zusätzliche Literatur
- Vermeiden von stillen Zustandsfehlern durch Veränderung von JavaScript-Referenzen — Erfahren Sie, warum die Veränderung von Objekten und Arrays über Referenzen die erneute Darstellung in React stört, warum das Spread-Operator nur eine oberflächliche Kopie erstellt und wie man Zustände sicher tief klonen kann.
- Verstehen von React Custom Hooks: Logikwiederverwendung ohne geteilten Zustand — Erfahren Sie, was React Custom Hooks sind, wie sie zustandsbasierte Logik zwischen Komponenten extrahieren und teilen sowie welche häufigen Fehler bei ihrer Erstellung vermieden werden sollten.