Zum Inhalt
wannacode

Zustand

Kurz gesagt: Zustand ist ein Wert, den sich eine Komponente zwischen zwei Renderings merkt; in React wird er mit useState gehalten, und jede Änderung rendert die Komponente neu.

Problem

Der Button zählt nicht

Die Produktkarte hat einen Button „Hinzufügen“ und einen Zähler „Im Warenkorb“. Klick dreimal auf „Hinzufügen“.

React

Die Konsole sagt count = 1, 2, 3. Die Karte sagt 0. Es gibt keinen Fehler. Die Variable count ändert sich, aber der Bildschirm folgt ihr nicht. Wo liegt der Unterschied?

Die Idee

Eine einfache Variable reicht nicht

Die Karte zeichnet nicht dein Code, sondern die Bibliothek. Sie zeichnet nur neu, wenn sie weiß, dass sich etwas geändert hat. count++ ändert die Variable, sagt es aber niemandem.

ProductCard ist eine Funktion. React ruft sie auf, wenn es die Karte zeichnet, und sie gibt zurück, was angezeigt werden soll. let count = 0 ist eine lokale Variable dieses Aufrufs. Der Klick ändert sie, aber niemand ruft die Funktion noch einmal auf, also bleibt die Karte bei 0.

Es gibt ein zweites Problem. Ruft React die Funktion aus einem anderen Grund noch einmal auf, ist die erste Zeile wieder let count = 0. Alles, was sich in der Variablen angesammelt hat, ist verloren.

Du brauchst Daten, die sich die Komponente zwischen zwei Renderings merkt und deren Änderung sie neu zeichnet. Das heißt Zustand (State).

Die Idee

useState und ref

const [count, setCount] = useState(0);
function add() {
setCount(count + 1);
}
  • useState(0) gibt zwei Dinge zurück: den Wert und die Funktion, die ihn ändert. Die Null ist nur der Startwert.
  • count ist der Wert für dieses Rendering.
  • setCount(1) merkt sich den neuen Wert und sagt React, dass es die Karte neu zeichnen soll. React ruft ProductCard noch einmal auf, und diesmal gibt useState 1 zurück.

useState ist ein Hook: eine React-Funktion, die oben in der Komponente aufgerufen wird, nie in einem if oder einer Schleife. React unterscheidet die Zustände an der Reihenfolge, in der sie aufgerufen werden.

Den Klick-Handler gibst du als Funktion an: onClick={add}. Schreibst du onClick={add()}, wird add aufgerufen, während die Karte gezeichnet wird. Es ändert den Zustand, der zeichnet neu, und React bricht mit dem Fehler „Too many re-renders“ ab.

React

Sieh es dir an

Sieh, was bei einem Klick passiert

function ProductCard({ product }) {
const [count, setCount] = useState(0);
function addThree() {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
}
return (
<article>
<button onClick={addThree}>+3</button>
<p>Im Warenkorb: {count}</p>
</article>
);
}
Schritt für Schritt, in Worten
  1. Die Karte hält count im Zustand von React: useState(0) liefert den Wert und die Funktion, die ihn ändert.
  2. React ruft die Funktion ProductCard auf. Sie fasst die Seite nicht an, sondern gibt eine Beschreibung zurück: einen Button und einen Text mit count = 0.
  3. Beim ersten Mal wird die Beschreibung zu echten Elementen im DOM. Die Seite zeigt jetzt „Im Warenkorb: 0“.
  4. Ein Klick auf +3: Der Browser ruft addThree auf.
  5. Dreimal setCount(count + 1). In diesem Aufruf ist count eine Momentaufnahme: 0. Jedes Mal landet „werde 1“ in der Warteschlange. Der Bildschirm ändert sich noch nicht.
  6. Der Handler ist fertig. React wendet die Warteschlange an: 1, 1, 1 ergibt 1. Dann ruft es ProductCard noch einmal auf, und jetzt ist count 1.
  7. React vergleicht die neue Beschreibung mit der alten. Der Button ist derselbe, nur der Text hat sich geändert. Im DOM ändert sich nur der Text.
  8. Deshalb zeigt der Zähler 1, nicht 3. Hängt der neue Wert vom alten ab, übergib eine Funktion: setCount((c) => c + 1).

Die Karte wird nicht neu gebaut. Die Bibliothek vergleicht die neue Beschreibung mit der alten und ändert im DOM nur den Unterschied: den Text. Der Button ist dasselbe Element, und der Fokus bleibt auf ihm. Das ist der Unterschied zu render() im DOM-Modul, das alles ersetzt hat.

Probier es aus

Repariere den Zähler

Das ist die Karte vom Anfang. Mach count zum Zustand, damit die Karte nach jedem Klick auf „Hinzufügen“ die neue Zahl zeigt.

React

Die Idee

Der Wert ist eine Momentaufnahme

Mineralwasser gibt es auch in einer Packung mit 6 Flaschen. Praktisch wäre, wenn der Packungs-Button sechsmal add() aufruft. Hier verhalten sich die beiden Bibliotheken unterschiedlich.

count ist eine Konstante eines Renderings. setCount ändert count nicht in dem Handler, der gerade läuft. Es legt eine Anforderung in eine Warteschlange, und React wendet sie nach dem Handler an:

function addBox() {
for (let i = 0; i < 6; i++) {
setCount(count + 1); // 0 + 1, sechsmal: „werde 1“
}
}

Die sechs Anforderungen lauten „werde 1“, und der Zähler wird 1, nicht 6. Sag stattdessen „addiere 1 zum letzten Wert“: Übergib eine Funktion statt eines Werts.

setCount((c) => c + 1);

React ruft jede solche Funktion mit dem Ergebnis der vorherigen auf: 0 → 1 → 2 … → 6. Die Regel: Hängt der neue Wert vom alten ab, übergib eine Funktion.

Probier es aus

Finde den Fehler

„Packung hinzufügen“ soll 6 Flaschen hinzufügen. Welche Zeile ist schuld?

Wähle die Zeile mit dem Fehler.

Probier es aus

Eine Packung Wasser

Schreibe addBox() so, dass „Packung hinzufügen (6 Stk.)“ 6 Flaschen zu dem addiert, was schon im Warenkorb ist. Die Prüfung klickt einmal auf „Hinzufügen“, dann zweimal auf die Packung und erwartet 1, 7 und 13.

React

Die Idee

Jede Karte merkt sich ihren eigenen Wert

Der Zustand gehört zur Instanz, nicht zum Code. Der Katalog hat zwei Karten aus derselben ProductCard, und jede hat ihr eigenes count:

React

Klick zweimal auf „Hinzufügen“ beim Brot und einmal bei der Milch: 2 und 1. Die Bibliothek hält den Zustand jeder Karte nach ihrem Platz im Baum der Komponenten. Nimmst du die Karte von der Seite, geht ihr Zustand mit. Wenn zwei Karten dieselbe Zahl zeigen müssen, muss sie in ihrem gemeinsamen Elternelement leben. Darum geht es in der Lektion über gemeinsamen Zustand in diesem Modul.

Genauer, für Neugierige

Warum eine Momentaufnahme. React ändert count nicht an Ort und Stelle, denn die Funktion wurde mit diesem Wert aufgerufen, und alles in ihr, auch die Handler, sieht diesen Wert. So ist ein Rendering in sich stimmig: Alles, was aus count berechnet wird, wird aus ein und demselben Wert berechnet.

Wann React nicht zeichnet. Bekommt setCount denselben Wert (verglichen mit Object.is), überspringt React das Rendering. Das ist in der nächsten Lektion bei Arrays wichtig.

Aktualisierungen werden gebündelt. Alle setCount-Aufrufe in einem Handler ergeben ein Rendering, und seit React 18 auch in setTimeout und nach await.

StrictMode. In der Entwicklung ruft <StrictMode> jede Komponente zweimal auf, um eine Komponente zu finden, die beim Zeichnen etwas außerhalb ändert. Die Sandbox nutzt ihn nicht, und ein Vite-Projekt hat ihn in main.jsx.

Klassenkomponenten. Älterer React-Code schreibt die Komponente als Klasse mit this.state und this.setState. Neuer Code nutzt Funktionen und Hooks.

Übung

Wähle die Menge

Der Katalog hat drei Karten. Füge in jeder eine Menge hinzu, die bei 1 beginnt, einen Button „−“ (Name für den Screenreader: „Weniger Weißbrot“), einen Button „+“ („Mehr Weißbrot“) und eine Zeile „2 × 1,20 € = 2,40 €“ mit Menge, Preis und Summe. „−“ ist deaktiviert, wenn die Menge 1 ist. Jede Karte zählt für sich.

React

Test

Teste dich

1. Warum ändern let count = 0 und count++ in der Komponente nichts am Bildschirm?
2. Was macht setCount(5)?
3. count ist 0. Der Handler ruft dreimal setCount(count + 1) auf. Was zeigt die Karte?
4. Warum ist onClick={add()} ein Fehler?
5. Die Seite hat zwei ProductCards, jede mit ihrem eigenen count. Die Kundin drückt in der ersten auf „Hinzufügen“. Was passiert?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·