Nach dieser Lektion sagen Sie voraus, was ein Handler nach dem Setzen von State sieht, erklären Batching und ändern denselben State mit Updater-Funktionen mehrmals.
Vorhersagen, dass ein Handler und ein Timer, den er startet, den State seines eigenen Renderns sehen
Batching erklären und das Ergebnis einer Warteschlange aus Werten und Updater-Funktionen vorhersagen
Reine Updater-Funktionen mit üblichen Namen schreiben und wissen, dass Strict Mode sie zweimal aufruft
Aufwärmen · Aufgabe 1 von 7
Zum Aufwärmen aus Lektion B3.3: Rendern und Committen. Ein Programm hält das <p> in einer Variablen, before, und klickt dann zweimal auf +10. Es gibt aus, ob das <p> auf der Seite noch before ist, und seinen Text. Was gibt es aus?
Der Handler setzt number auf number + 5 und startet dann einen 50-ms-Timer, der number ausgibt. Der Timer läuft nach dem erneuten Rendern. Was wird ausgegeben, und was zeigt das <h1>? (ausgegeben / angezeigt)
Ordnen Sie jedem Stück Code in einem Klick-Handler zu, was es bedeutet.
Üben · Aufgabe 5 von 7
Ergänzen Sie das Argument für jeden der drei Aufrufe, sodass ein Klick 3 addiert (und zwei Klicks 6 zeigen).
function handleClick() {
setNumber(____);
setNumber(____);
setNumber(____);
}
setNumber();
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Der Handler mischt Updater-Funktionen und einen Wert. number beginnt bei 0. Sie klicken einmal. Was zeigt das <h1>?
function Counter() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button
onClick={() => {
setNumber(n => n + 1);
setNumber(number + 5);
setNumber(n => n * 2);
}}
>
Go
</button>
</>
);
}
Anwenden · Aufgabe 7 von 7
Mini-Aufgabe: ein Punktezähler. Schreiben Sie in main.jsx eine Komponente ScoreKeeper mit einem State score, beginnend bei 0, angezeigt in einem <h1>. Ein Button +3 addiert 3 mit drei Set-Aufrufen. Ein Button Restart at 1 setzt den Punktestand auf 0 und addiert dann 1, in zwei Set-Aufrufen. Führen Sie es aus, klicken Sie zweimal auf +3, dann auf Restart at 1. Notieren Sie für jeden Handler die Warteschlange, die React abarbeitet.
Prüfen Sie Ihr Ergebnis anhand dieser Liste
Selbst programmieren
Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.
Ausgearbeitetes Beispiel
Drei Set-Aufrufe mit Werten, dann mit Updatern
Counter hat zwei Buttons, die beide 3 addieren wollen. Der eine übergibt Werte, setNumber(number + 1); der andere Updater-Funktionen, setNumber(n => n + 1). Jeder Handler gibt nach seinen drei Aufrufen außerdem number aus. Ein Lauf hängt die Komponente nur ein, also klicken Timer unten in main.jsx jeden Button einmal und geben nach dem erneuten Rendern die Überschrift aus. Im Browser bleibt die Seite aktiv: Klicken Sie die Buttons selbst.
main.jsx
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
function addThreeValues() {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
console.log('values: number is still', number);
}
function addThreeUpdaters() {
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
console.log('updaters: number is still', number);
}
return (
<>
<h1>{number}</h1>
<button onClick={addThreeValues}>+3 with values</button>
<button onClick={addThreeUpdaters}>+3 with updaters</button>
</>
);
}
// A run only mounts. Act like a user: click each button once.
setTimeout(() => {
const [values, updaters] = document.querySelectorAll('button');
values.click();
setTimeout(() => {
console.log('after values:', document.querySelector('h1').textContent);
updaters.click();
setTimeout(() => {
console.log('after updaters:', document.querySelector('h1').textContent);
}, 50);
}, 50);
}, 50);
Ausführen mit
npm run dev
Ausgabe
values: number is still 0
after values: 1
updaters: number is still 1
after updaters: 4
<!-- #root -->
<div id="root"><h1>4</h1><button>+3 with values</button><button>+3 with updaters</button></div>
In beiden Handlern ist number noch der Wert ihres eigenen Renderns: State zu setzen ändert ihn nicht.
Drei Werte sind dreimal „ersetze durch 0 + 1“, also addiert der erste Klick nur 1.
Drei Updater werden eingereiht und laufen der Reihe nach, 1 + 1, 2 + 1, 3 + 1, also addiert der zweite Klick 3.
Jeder Handler gibt pro Klick einmal aus; nur Komponentenrümpfe und Updater laufen in Strict Mode zweimal.
Ändern und ausführen
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.
Ihre Seite
Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.
Führen Sie das Programm aus, um seine Seite hier zu sehen.
Übungen
Übung 1 von 2
Den +3-Button reparieren
Counter hat einen Button +3, der nur 1 addiert, und einen Button „Double, then +1“, der 3 zu 7 machen soll, es aber nicht tut. Beide Handler übergeben Werte, berechnet aus number dieses Renderns. Ändern Sie sie in Updater-Funktionen, sodass jeder Set-Aufruf auf dem vorigen aufbaut. Behalten Sie die Beschriftungen der Buttons.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.
Ihre Seite
Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.
Führen Sie das Programm aus, um seine Seite hier zu sehen.
Hinweise
Hinweis 1
number ist in jeder Zeile eines Handlers gleich. Welche Art von Argument bekommt stattdessen den bisherigen State?
Hinweis 2
Schreiben Sie setNumber(n => n + 1) für jeden Schritt von +3.
Hinweis 3
Double, then +1 sind zwei Updater: setNumber(n => n * 2); dann setNumber(n => n + 1);.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
function handlePlusThree() {
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
}
function handleDoublePlusOne() {
setNumber(n => n * 2);
setNumber(n => n + 1);
}
return (
<>
<h1>{number}</h1>
<button onClick={handlePlusThree}>+3</button>
<button onClick={handleDoublePlusOne}>Double, then +1</button>
</>
);
}
Auf dem eigenen Computer ausführen
Installieren Sie React 19.3 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.jsx
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
// TODO: +3 only adds 1, and "Double, then +1" is wrong too.
function handlePlusThree() {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}
function handleDoublePlusOne() {
setNumber(number * 2);
setNumber(number + 1);
}
return (
<>
<h1>{number}</h1>
<button onClick={handlePlusThree}>+3</button>
<button onClick={handleDoublePlusOne}>Double, then +1</button>
</>
);
}
main.test.jsx
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './main.jsx';
const number = () => screen.getByRole('heading', {level: 1}).textContent;
const button = (name) => screen.getByRole('button', {name});
test('Ein Klick auf +3 zeigt 3', async () => {
const user = userEvent.setup();
render(<Counter />);
await user.click(button('+3'));
assert.equal(number(), '3', 'nach einem Klick auf +3 zeigt der Zähler ' + JSON.stringify(number()));
});
test('Drei Klicks auf +3 zeigen 9', async () => {
const user = userEvent.setup();
render(<Counter />);
for (let i = 0; i < 3; i++) await user.click(button('+3'));
assert.equal(number(), '9', 'nach drei Klicks auf +3 zeigt der Zähler ' + JSON.stringify(number()));
});
test('+3, dann Double, then +1, zeigt 7', async () => {
const user = userEvent.setup();
render(<Counter />);
await user.click(button('+3'));
await user.click(button('Double, then +1'));
assert.equal(number(), '7', 'von 3 aus zeigt Double, then +1 ' + JSON.stringify(number()));
});
Legen Sie main.jsx in einem Vite-React-Projekt (npm create vite@latest, Vorlage react) als App.jsx in src/ ab und starten Sie es mit npm run dev. React rendert es dort wie hier in <StrictMode>.
Programm ausführen:
npm run dev
Für die Prüfungen gibt es noch keinen Befehl auf Ihrem Computer. Sie stehen in main.test.jsx, geschrieben mit node:test und React Testing Library.
Übung 2 von 2
Einen Bestellzähler reparieren
RequestTracker zählt Bestellungen: Jedes Buy fügt eine offene Bestellung hinzu, und nach einer kurzen Wartezeit wandert sie von Pending zu Completed. Klicken Sie auf Buy: Pending zeigt 1, dann -1. Klicken Sie zweimal schnell, geht eine Bestellung verloren. Nach dem await verwendet der Handler noch pending und completed des Renderns, in dem er begann. Reparieren Sie beide Zähler mit Updater-Funktionen.
Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.
Beim ersten Ausführen lädt Ihr Browser React und den Compiler herunter, der JSX in JavaScript übersetzt (bis zu 2 MB), und speichert beides im Cache. Ihre Komponenten laufen in Ihrem Browser und bleiben auf Ihrem Gerät.
Ihre Seite
Ihre Seite läuft in einem abgeschotteten Rahmen. Sie hat keinen Zugriff auf diese Website, und ihr localStorage liegt nur für diesen Lauf im Speicher: Der nächste Lauf beginnt leer.
Führen Sie das Programm aus, um seine Seite hier zu sehen.
Hinweise
Hinweis 1
Nach await delay(50) sind pending und completed der Schnappschuss aus dem Rendern des Klicks, nicht der aktuelle State.
Hinweis 2
Ein Updater bekommt den State so, wie er ist, wenn React ihn verarbeitet: setPending(p => p - 1).
Hinweis 3
Ändern Sie alle drei Set-Aufrufe: setPending(p => p + 1), setPending(p => p - 1) und setCompleted(c => c + 1).
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
import { useState } from 'react';
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
export default function RequestTracker() {
const [pending, setPending] = useState(0);
const [completed, setCompleted] = useState(0);
async function handleClick() {
setPending(p => p + 1);
await delay(50);
setPending(p => p - 1);
setCompleted(c => c + 1);
}
return (
<>
<h3>Pending: {pending}</h3>
<h3>Completed: {completed}</h3>
<button onClick={handleClick}>Buy</button>
</>
);
}
Auf dem eigenen Computer ausführen
Installieren Sie React 19.3 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.
main.jsx
import { useState } from 'react';
function delay(ms) {
return new Promise(resolve => {
setTimeout(resolve, ms);
});
}
export default function RequestTracker() {
const [pending, setPending] = useState(0);
const [completed, setCompleted] = useState(0);
// TODO: after the delay, Pending goes to -1, and quick clicks lose orders.
async function handleClick() {
setPending(pending + 1);
await delay(50);
setPending(pending - 1);
setCompleted(completed + 1);
}
return (
<>
<h3>Pending: {pending}</h3>
<h3>Completed: {completed}</h3>
<button onClick={handleClick}>Buy</button>
</>
);
}
main.test.jsx
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen, waitFor} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import RequestTracker from './main.jsx';
const pending = () => screen.getByText(/^Pending:/).textContent;
const completed = () => screen.getByText(/^Completed:/).textContent;
const buy = () => screen.getByRole('button', {name: 'Buy'});
test('Buy zeigt sofort eine offene Bestellung', async () => {
const user = userEvent.setup();
render(<RequestTracker />);
await user.click(buy());
assert.equal(pending(), 'Pending: 1', 'direkt nach Buy zeigt die Seite ' + JSON.stringify(pending()));
});
test('Nach der Wartezeit ist die Bestellung erledigt und nichts mehr offen', async () => {
const user = userEvent.setup();
render(<RequestTracker />);
await user.click(buy());
await waitFor(() => assert.equal(completed(), 'Completed: 1', 'nach der Wartezeit zeigt die Seite ' + JSON.stringify(completed())));
assert.equal(pending(), 'Pending: 0', 'nach der Wartezeit zeigt die Seite ' + JSON.stringify(pending()));
});
test('Zwei schnelle Klicks erledigen zwei Bestellungen', async () => {
const user = userEvent.setup();
render(<RequestTracker />);
await user.click(buy());
await user.click(buy());
await waitFor(() => assert.equal(completed(), 'Completed: 2', 'nach zwei Bestellungen zeigt die Seite ' + JSON.stringify(completed())));
assert.equal(pending(), 'Pending: 0', 'nach zwei Bestellungen zeigt die Seite ' + JSON.stringify(pending()));
});
Legen Sie main.jsx in einem Vite-React-Projekt (npm create vite@latest, Vorlage react) als App.jsx in src/ ab und starten Sie es mit npm run dev. React rendert es dort wie hier in <StrictMode>.
Programm ausführen:
npm run dev
Für die Prüfungen gibt es noch keinen Befehl auf Ihrem Computer. Sie stehen in main.test.jsx, geschrieben mit node:test und React Testing Library.
Häufige Fehler
Das Argument des Updaters ohne Pfeil verwenden
import { useState } from 'react';
export default function Counter() {
const [number, setNumber] = useState(0);
return (
<button onClick={() => setNumber(n + 1)}>{number}</button>
);
}
// Act like a user: click once.
setTimeout(() => document.querySelector('button').click(), 50);
Was React ausgibt
ReferenceError: n is not defined
Warum, und die Lösung
n gibt es nur als Parameter einer Updater-Funktion. Ohne n => liest setNumber(n + 1) eine Variable n, die niemand deklariert hat; der Klick wirft also einen nicht abgefangenen ReferenceError, und nichts ändert sich. Schreiben Sie den Updater vollständig: setNumber(n => n + 1).
Das Ergebnis von push aus einem Updater zurückgeben
Ein Updater muss den nächsten State zurückgeben. push ändert das Array an Ort und Stelle und gibt seine neue Länge zurück, eine Zahl; tags wird also zur Zahl, und das Rendern wirft (Formulierung von Chrome und Node.js; Firefox und Safari formulieren es anders). Das Array zu ändern ist außerdem unrein, und Strict Mode führt den Updater zweimal aus. Geben Sie ein neues Array zurück: setTags(t => [...t, 'state']).
return in einem Updater mit geschweiften Klammern vergessen
TypeError: Cannot read properties of undefined (reading 'map')
Warum, und die Lösung
Mit geschweiften Klammern braucht eine Pfeilfunktion return; ohne gibt der Updater undefined zurück, das wird der nächste State, und das Rendern wirft (Formulierung von Chrome und Node.js; Firefox und Safari formulieren es anders). Lassen Sie die Klammern weg, t => [...t, 'state'], oder schreiben Sie t => { return [...t, 'state']; }.
React im Browser: React 19.3.0 (Entwicklungs-Build) und React Testing Library, MIT, von dieser Website ausgeliefert; JSX übersetzt TypeScript 6.0.3, Apache-2.0. Lizenz und Quellcode
Abschlussquiz
5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.
Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.
Kernideen
Jedes Rendern bekommt einen Schnappschuss des States
State zu setzen ändert nicht die Variable, die Sie schon haben; es fordert ein neues Rendern an. Ruft React Ihre Komponente auf, übergibt es ihr den State für dieses Rendern, und JSX, Props und Event-Handler werden alle daraus berechnet. Im Handler eines Renderns bleibt number also 0, auch nach setNumber(number + 1): Erst das nächste Rendern sieht 1. Ein Timer, den dieser Handler startet, liest dieselbe 0, auch wenn er nach dem erneuten Rendern läuft. react.dev schlägt vor, die Werte einzusetzen: In diesem Rendern liest der Handler setNumber(0 + 1). Jedes Rendern hat eigene Event-Handler mit eigenem Schnappschuss.
Batching und die Warteschlange der Updates
React wartet, bis der gesamte Code eines Event-Handlers gelaufen ist, verarbeitet dann die State-Updates und rendert einmal. Das ist Batching: Es vermeidet halb fertige Rendervorgänge und zu viele Rendervorgänge. Jeder Klick wird für sich behandelt. Jeder Set-Aufruf fügt einer Warteschlange einen Eintrag hinzu. Ein Wert wie setNumber(number + 1) oder setNumber(5) heißt „ersetze durch diesen Wert“. Eine Updater-Funktion wie setNumber(n => n + 1) heißt „nimm den bisherigen State und berechne den nächsten“. Beim nächsten Rendern arbeitet React die Warteschlange der Reihe nach ab: Drei Updater ab 0 ergeben 3; 5, dann n => n + 1 ergibt 6; ein setNumber(42) danach ergibt 42.
Updater-Funktionen: rein und nach Konvention benannt
Verwenden Sie einen Updater, wenn der nächste State vom vorigen abhängt und Sie ihn in einem Ereignis mehrmals ändern, oder nach einem await, wo der Schnappschuss veraltet ist. Updater laufen während des Renderns, also müssen sie rein sein: nur den nächsten State zurückgeben, keine Set-Aufrufe und keine anderen Seiteneffekte darin. In der Entwicklung führt Strict Mode jeden Updater zweimal aus und verwirft das zweite Ergebnis; ein console.log darin erscheint also doppelt, und ein unreiner Updater zeigt seinen Fehler. Benennen Sie das Argument nach dem State: setEnabled(e => !e), setCount(c => c + 1), oder mit vollem Namen, setEnabled(enabled => !enabled). setCount((c) => c + 1) aus B1.3 war ein Updater.