Nach dieser Lektion schreiben Sie Event-Handler, geben sie als Props weiter, sagen die Reihenfolge verschachtelter Handler voraus und stoppen Propagation oder ein Neuladen.
Sagen Sie voraus, bevor Sie weiterlesen. Das <div> und der Button haben beide ein onClick. Sie klicken auf Play. Was wird ausgegeben, in welcher Reihenfolge?
Jeder LogButton gibt sein eigenes Prop message aus. Sie klicken auf den zweiten Button, Share. Was wird ausgegeben?
function LogButton({ message, children }) {
return (
<button onClick={() => console.log(message)}>
{children}
</button>
);
}
function Actions() {
return (
<div>
<LogButton message="Saving!">Save</LogButton>
<LogButton message="Sharing!">Share</LogButton>
</div>
);
}
Denksport · Aufgabe 6 von 7
Knobelaufgabe. Das ist das Beispiel weiter unten. Die Toolbar hat onClickCapture und onClick, und Button stoppt die Propagation. Sie klicken auf Upload Image. Was wird ausgegeben, in welcher Reihenfolge?
Mini-Aufgabe: eine Karte mit Like-Button. Schreiben Sie in main.jsx eine Komponente Card mit den Props title, onOpen und onLike. Sie rendert ein <article>, das beim Klick onOpen aufruft, mit dem Titel in einem <h2> und einem Like-Button darin. Der Like-Button ruft onLike auf, und ein Klick darauf darf die Karte nicht zusätzlich öffnen. Der Default-Export App rendert eine Card und übergibt Handler, die "open card" und "liked" ausgeben. Führen Sie es aus, klicken Sie auf Like, dann auf den Titel: Sie sehen liked, dann open card.
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
Eine Toolbar, die Klicks fängt und stoppt
App übergibt Toolbar zwei Handler als onPlayMovie und onUploadImage, benannt nach dem, was sie tun. Toolbar reicht onPlayMovie an einen einfachen <button> weiter und onUploadImage an Button, der e.stopPropagation() und dann den Handler aufruft. Das <div> der Toolbar hat ein onClickCapture und ein onClick. Ein Lauf rendert nur die Seite, die Ausgabe ist also das HTML; Handler erscheinen darin nicht. Im Browser bleibt die Seite aktiv: Klicken Sie auf die Buttons, und ihre Zeilen kommen zur Ausgabe hinzu.
Vor dem Snapshot wird nichts ausgegeben: Die Handler laufen nur beim Klick, nie beim Rendern.
Ein Klick auf Play Movie gibt capture: toolbar, Playing!, bubble: toolbar aus: nach unten, der Button, dann nach oben.
Ein Klick auf Upload Image gibt capture: toolbar, Uploading! aus: Button stoppt den Weg nach oben, nicht den Capture-Handler.
Ein Klick auf die Toolbar zwischen den Buttons gibt capture: toolbar, bubble: toolbar aus.
Ä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
Der Toolbar den Klick nicht überlassen
Die Toolbar zeigt eine Statuszeile. Ein Klick auf Play Movie soll Playing! zeigen, Upload Image soll Uploading! zeigen. Führen Sie es aus und klicken Sie: Am Ende steht immer You clicked the toolbar, weil der Klick nach dem Handler des Buttons zum <div> aufsteigt. Ändern Sie Button so, dass er die Propagation stoppt und dann onClick aufruft. Ein Klick auf die Toolbar selbst, etwa auf die Statuszeile, muss weiterhin You clicked the toolbar zeigen.
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
Button braucht einen eigenen Handler: onClick={e => { … }}. Er bekommt das Event-Objekt e.
Hinweis 2
Rufen Sie darin zuerst e.stopPropagation(); auf, dann onClick();.
Hinweis 3
Lassen Sie das onClick der Toolbar, wie es ist: Klicks auf die Toolbar selbst sollen sie weiterhin erreichen.
Eine Lösung zeigen
Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.
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 Button({ onClick, children }) {
// TODO: stop the click from reaching the toolbar, then call onClick.
return <button onClick={onClick}>{children}</button>;
}
export default function Toolbar() {
const [message, setMessage] = useState('Nothing clicked yet');
return (
<div
role="toolbar"
aria-label="Player"
className="toolbar"
onClick={() => setMessage('You clicked the toolbar')}
>
<Button onClick={() => setMessage('Playing!')}>Play Movie</Button>
<Button onClick={() => setMessage('Uploading!')}>Upload Image</Button>
<p role="status">{message}</p>
</div>
);
}
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 Toolbar from './main.jsx';
const status = () => screen.getByRole('status').textContent;
test('Play Movie zeigt Playing!', async () => {
const user = userEvent.setup();
render(<Toolbar />);
await user.click(screen.getByRole('button', {name: 'Play Movie'}));
assert.equal(status(), 'Playing!', 'nach einem Klick auf Play Movie lautet der Status ' + JSON.stringify(status()));
});
test('Upload Image zeigt Uploading!', async () => {
const user = userEvent.setup();
render(<Toolbar />);
await user.click(screen.getByRole('button', {name: 'Upload Image'}));
assert.equal(status(), 'Uploading!', 'nach einem Klick auf Upload Image lautet der Status ' + JSON.stringify(status()));
});
test('Ein Klick auf die Toolbar selbst erreicht die Toolbar weiterhin', async () => {
const user = userEvent.setup();
render(<Toolbar />);
await user.click(screen.getByRole('status'));
assert.equal(status(), 'You clicked the toolbar', 'nach einem Klick auf die Statuszeile lautet sie ' + JSON.stringify(status()));
});
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
Ein Formular, das nicht neu lädt
Signup hat ein Namensfeld und einen Send-Button. Ein abgeschicktes Formular lässt den Browser standardmäßig eine neue Seite laden. In jsdom, das die Prüfungen verwenden, gibt das Not implemented: HTMLFormElement's requestSubmit() method aus. Vervollständigen Sie handleSubmit: Rufen Sie e.preventDefault() auf dem Event auf und speichern Sie dann den eingegebenen Namen mit setSent(name), sodass die Statuszeile Submitted: und den Namen zeigt.
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
handleSubmit bekommt das Event-Objekt bereits als e.
Hinweis 2
Die erste Zeile ist e.preventDefault();. Sie bricht das Standardverhalten des Browsers ab, nicht die Propagation.
Hinweis 3
Rufen Sie dann setSent(name); auf: name enthält die Eingabe, und sent wird in der Statuszeile gezeigt.
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 Signup() {
const [name, setName] = useState('');
const [sent, setSent] = useState('');
function handleSubmit(e) {
e.preventDefault();
setSent(name);
}
return (
<form aria-label="Sign up" onSubmit={handleSubmit}>
<label>
Name <input value={name} onChange={e => setName(e.target.value)} />
</label>
<button>Send</button>
<p role="status">{sent ? 'Submitted: ' + sent : 'Nothing sent yet'}</p>
</form>
);
}
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 Signup() {
const [name, setName] = useState('');
const [sent, setSent] = useState('');
function handleSubmit(e) {
// TODO: stop the browser from loading a new page,
// then remember the name that was sent.
}
return (
<form aria-label="Sign up" onSubmit={handleSubmit}>
<label>
Name <input value={name} onChange={e => setName(e.target.value)} />
</label>
<button>Send</button>
<p role="status">{sent ? 'Submitted: ' + sent : 'Nothing sent yet'}</p>
</form>
);
}
main.test.jsx
import {test} from 'node:test';
import assert from 'node:assert/strict';
import {render, screen, fireEvent} from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Signup from './main.jsx';
const status = () => screen.getByRole('status').textContent;
test('Vor dem Senden lautet der Status Nothing sent yet', () => {
render(<Signup />);
assert.equal(status(), 'Nothing sent yet', 'der Status lautet ' + JSON.stringify(status()));
});
test('Senden zeigt den eingegebenen Namen', async () => {
const user = userEvent.setup();
render(<Signup />);
await user.type(screen.getByRole('textbox', {name: 'Name'}), 'Ada');
await user.click(screen.getByRole('button', {name: 'Send'}));
assert.equal(status(), 'Submitted: Ada', 'nach Eingabe von Ada und Senden lautet der Status ' + JSON.stringify(status()));
});
test('Der Submit-Handler verhindert das Standardverhalten', () => {
render(<Signup />);
const notPrevented = fireEvent.submit(screen.getByRole('form', {name: 'Sign up'}));
assert.equal(notPrevented, false, 'das Submit-Event wurde nicht verhindert, der Browser würde also eine neue Seite laden');
});
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
Ein String statt einer Funktion
export default function Save() {
function handleClick() {
console.log('saved');
}
return <button onClick="handleClick()">Save</button>;
}
Was React ausgibt
Expected `onClick` listener to be a function, instead got a value of `string` type.
Warum, und die Lösung
In HTML enthält onclick Code als Text. In React nimmt onClick eine Funktion, übergeben Sie die Funktion also in geschweiften Klammern: onClick={handleClick}. React warnt, sobald der Button rendert, und der String wird nie ausgeführt.
TypeError: Cannot read properties of undefined (reading 'preventDefault')
Warum, und die Lösung
Die Klammern rufen handleSubmit auf, während Signup rendert, ohne Event; e ist also undefined, und der Lauf stoppt mit Uncaught TypeError (Formulierung von Chrome und Node.js; Firefox und Safari formulieren es anders). Übergeben Sie die Funktion, onSubmit={handleSubmit}: React ruft sie beim Absenden auf und gibt ihr das Event-Objekt.
Ein eigener Eventname an einem eingebauten Tag
export default function Player() {
return (
<button onPlayMovie={() => console.log('Playing!')}>
Play Movie
</button>
);
}
Was React ausgibt
Unknown event handler property `onPlayMovie`. It will be ignored.
Warum, und die Lösung
Die Event-Props Ihrer eigenen Komponenten dürfen Sie benennen, wie Sie möchten, aber ein eingebauter <button> kennt nur Browser-Events wie onClick. Verwenden Sie onClick am <button>; geben Sie Ihrer eigenen Komponente Toolbar ein Prop namens onPlayMovie und reichen Sie es weiter: <button onClick={onPlayMovie}>.
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
Eine Funktion übergeben, nicht aufrufen
Ein Event-Handler ist Ihre eigene Funktion, die React aufruft, wenn etwas geschieht, etwa ein Klick. Definieren Sie ihn in der Komponente, nach Konvention benannt als handle plus Event: handleClick. Übergeben Sie ihn mit onClick={handleClick}. Ein kurzer Handler kann inline stehen: onClick={() => console.log('saved')}. Beide übergeben eine Funktion. onClick={handleClick()} und onClick={console.log('saved')} rufen sie sofort auf, bei jedem Rendern, nicht beim Klick. Weil ein Handler in der Komponente deklariert ist, kann er ihre Props lesen. Anders als das Rendern müssen Handler nicht rein sein: Sie sind der beste Ort für Nebenwirkungen.
Handler wandern als Props nach unten
Oft entscheidet die Elternkomponente, was ein Klick tun soll. Das Kind nimmt ein Prop an und reicht es weiter: function Button({ onClick, children }) rendert <button onClick={onClick}>. Bei Ihren eigenen Komponenten wählen Sie die Prop-Namen. Nach Konvention beginnen sie mit on und einem Großbuchstaben und benennen, was in Ihrer App geschieht: <Toolbar onPlayMovie={…} onUploadImage={…} />. Die Elternkomponente muss nicht wissen, wie Toolbar sie auslöst. Eingebaute Tags wie <button> und <div> kennen nur Browser-Eventnamen wie onClick; vor einem unbekannten, etwa onPlayMovie an einem <button>, warnt React und ignoriert ihn.
Events steigen auf; Sie können sie stoppen
Ein Klick auf einen Button in einem <div> führt zuerst das onClick des Buttons aus, dann das des div: Das Event propagiert im Baum nach oben. In React tut das jedes Event außer onScroll. Ein Handler bekommt das Event-Objekt, meist e genannt. e.stopPropagation() hält das Event von Handlern weiter oben fern. e.preventDefault() hat damit nichts zu tun: Es stoppt das eigene Standardverhalten des Browsers, etwa dass ein abgeschicktes Formular eine neue Seite lädt. Ein onClickCapture-Handler läuft auf dem Weg nach unten, vor jedem onClick, selbst wenn ein Kind die Propagation stoppt. Statt sich auf das Aufsteigen zu verlassen, kann ein Kind das Handler-Prop der Eltern selbst aufrufen; das ist leichter nachzuvollziehen.