SyntheticEvent, ChangeEvent und MouseEventHandler ohne any
Ein Event-Handler ohne korrekten Typ verleitet fast automatisch zu any oder zu wiederholten Typumwandlungen, sobald auf event.target.value zugegriffen wird. React Event Handler Types lösen dieses Problem an der Wurzel: SyntheticEvent, ChangeEvent und die passenden Handler-Typen wie MouseEventHandler kennen exakt, welches Element ein Event ausgelöst hat und welche Eigenschaften darauf verfügbar sind. Dieser Artikel zeigt, wie man Event Handler Types in React systematisch und ohne any einsetzt.
Inhaltsverzeichnis
- 1. Warum React-Events eigene Typen brauchen
- 2. SyntheticEvent verstehen: React-Event vs. natives DOM-Event
- 3. Die häufigsten Handler-Typen: onClick, onChange, onSubmit
- 4. Generische Event-Handler für Formularfelder
- 5. currentTarget vs. target typsicher unterscheiden
- 6. Event-Handler als Props: MouseEventHandler und Co.
- 7. Keyboard- und Focus-Events typsicher behandeln
- 8. Eigene Callback-Signaturen statt nativer Events
- 9. Event-Handler-Typisierung im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Warum React-Events eigene Typen brauchen
React reicht Browser-Events nicht direkt an Event-Handler weiter, sondern verpackt sie in ein eigenes Wrapper-Objekt, das SyntheticEvent. Dieses Wrapper-Objekt normalisiert Unterschiede zwischen Browsern und sorgt für konsistentes Verhalten über alle Zielplattformen hinweg. Für die Typisierung bedeutet das, dass native DOM-Typen wie Event oder MouseEvent aus dem lib.dom.d.ts nicht automatisch zu React-Events passen, sondern eigene, React-spezifische Event Handler Types nötig sind, die React selbst über das @types/react-Paket bereitstellt.
Ohne diese spezifischen Typen greifen viele Entwickler entweder zu any für den Event-Parameter oder zu einem zu allgemeinen Event, was beides dazu führt, dass Properties wie event.target.value nicht mehr typgeprüft sind. Ein Tippfehler in der Property-Kette fällt dann erst zur Laufzeit auf, oft als undefined, das stillschweigend weiterverarbeitet wird, statt als Compile-Fehler direkt im Editor sichtbar zu werden. Die korrekten React Event Handler Types verhindern genau das, weil sie exakt beschreiben, welches Element den Event ausgelöst hat und welche Eigenschaften auf diesem Element existieren.
Ein weiterer Grund für präzise Event-Typen ist die Autovervollständigung im Editor. Sobald ein Event-Handler korrekt typisiert ist, schlägt der Editor bei event. exakt die verfügbaren Properties vor, inklusive spezifischer Felder wie key bei Keyboard-Events oder clientX bei Maus-Events. Diese Unterstützung verschwindet komplett, sobald der Parameter als any deklariert ist.
2. SyntheticEvent verstehen: React-Event vs. natives DOM-Event
SyntheticEvent ist die Basisklasse aller React-Event-Typen und bildet die gemeinsame Schnittmenge an Properties ab, die praktisch jedes Event besitzt: preventDefault(), stopPropagation(), target und currentTarget. Für konkrete Event-Arten existieren spezialisierte Subtypen wie MouseEvent, KeyboardEvent, ChangeEvent und FormEvent, jeweils aus dem react-Paket importiert, nicht aus den globalen DOM-Typdefinitionen. Der Namenskonflikt mit den gleichnamigen nativen DOM-Typen ist dabei beabsichtigt: React signalisiert damit, dass es sich um die React-spezifische Variante handelt, weshalb der Import explizit aus react erfolgen muss.
Ein häufiges Missverständnis betrifft die Wiederverwendung von Event-Objekten außerhalb des synchronen Handler-Aufrufs, etwa in einem setTimeout oder nach einem await. In älteren React-Versionen wurden SyntheticEvent-Objekte nach dem Handler-Aufruf recycelt, in aktuellen Versionen ist dieses Pooling entfernt, aber der Zugriff auf event.target nach einem asynchronen Vorgang bleibt trotzdem riskant, weil das zugrunde liegende DOM-Element sich zwischenzeitlich verändert haben kann. Wer einen Wert aus dem Event über eine asynchrone Grenze hinweg braucht, sollte ihn deshalb synchron in eine lokale Variable extrahieren.
import type { ChangeEvent, MouseEvent, FormEvent } from "react";
// SyntheticEvent types are imported from "react", not from lib.dom.d.ts
function handleClick(event: MouseEvent<HTMLButtonElement>) {
event.preventDefault();
console.log(event.currentTarget.name); // typed: HTMLButtonElement
}
function handleChange(event: ChangeEvent<HTMLInputElement>) {
console.log(event.target.value); // typed: string
}
function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
// Extract needed values synchronously before any async boundary
const formData = new FormData(event.currentTarget);
console.log(formData.get("email"));
}
3. Die häufigsten Handler-Typen: onClick, onChange, onSubmit
Für die drei häufigsten Interaktionen in React-Anwendungen gibt es feste, gut dokumentierte Event Handler Types: MouseEvent<HTMLButtonElement> für Klick-Handler auf Buttons, ChangeEvent<HTMLInputElement> für Änderungen an Eingabefeldern, und FormEvent<HTMLFormElement> für das Absenden eines Formulars. Der generische Typparameter in spitzen Klammern gibt dabei jeweils an, an welchem HTML-Element der Handler tatsächlich registriert ist, und bestimmt darüber den konkreten Typ von event.currentTarget.
Wichtig ist, den Typparameter korrekt auf das tatsächliche Element abzustimmen: Ein onClick-Handler an einem <div> braucht MouseEvent<HTMLDivElement>, nicht HTMLButtonElement, sonst schlägt der Compiler beim Zugriff auf button-spezifische Properties wie disabled fehl, oder schlimmer, lässt einen falschen Zugriff unbemerkt zu, wenn beide Elementtypen zufällig dieselbe Property besitzen. Diese Präzision zahlt sich vor allem bei komplexeren Interaktionen aus, in denen ein Handler an mehreren unterschiedlichen Elementtypen registriert werden könnte.
import { useState } from "react";
import type { ChangeEvent, FormEvent } from "react";
function LoginForm() {
const [email, setEmail] = useState("");
// Typed to the exact element the handler is attached to
const handleEmailChange = (event: ChangeEvent<HTMLInputElement>) => {
setEmail(event.target.value);
};
const handleSubmit = (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
console.log("Submitting", email);
};
return (
<form onSubmit={handleSubmit}>
<input type="email" value={email} onChange={handleEmailChange} />
<button type="submit">Anmelden</button>
</form>
);
}
4. Generische Event-Handler für Formularfelder
Formulare mit vielen einzelnen Feldern führen häufig zu wiederholten, fast identischen onChange-Handlern, jeweils nur mit einem anderen Feldnamen im setState-Aufruf. Statt für jedes Feld einen eigenen Handler zu schreiben, lässt sich ein einziger, generischer Handler implementieren, der den Feldnamen aus dem name-Attribut des Eingabeelements abliest. Der Event Handler Type bleibt dabei unverändert ChangeEvent<HTMLInputElement>, während der generische Zustandstyp T beschreibt, welche Felder das jeweilige Formular tatsächlich besitzt.
Diese Technik reduziert Duplizierung erheblich, verlangt aber eine bewusste Entscheidung an einer Stelle: Der Zugriff auf event.target.name als Schlüssel für ein generisches Zustandsobjekt ist von Natur aus etwas weniger streng typisiert als ein Handler pro Feld, weil TypeScript zur Kompilierzeit nicht garantieren kann, dass jeder name-Wert im JSX auch tatsächlich einem Feld im Zustandstyp entspricht. Diesen Kompromiss sollte man bewusst eingehen, meist über eine einzelne, gut sichtbare Typ-Assertion an der Stelle, an der der Feldname als Schlüssel verwendet wird, statt die Unsicherheit unbemerkt durch die gesamte Komponente zu tragen.
import { useState } from "react";
import type { ChangeEvent } from "react";
interface RegistrationForm {
email: string;
username: string;
city: string;
}
function RegistrationFields() {
const [form, setForm] = useState<RegistrationForm>({ email: "", username: "", city: "" });
// One generic handler instead of one handler per field
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
setForm((prev) => ({ ...prev, [name as keyof RegistrationForm]: value }));
};
return (
<>
<input name="email" value={form.email} onChange={handleChange} />
<input name="username" value={form.username} onChange={handleChange} />
<input name="city" value={form.city} onChange={handleChange} />
</>
);
}
5. currentTarget vs. target typsicher unterscheiden
Ein Detail, das bei der Typisierung von Event-Handlern regelmäßig übersehen wird, ist der Unterschied zwischen event.target und event.currentTarget. currentTarget ist immer genau das Element, an dem der Handler registriert wurde, und trägt exakt den generischen Typparameter, der beim Event-Typ angegeben wurde, etwa HTMLButtonElement. target dagegen ist das Element, das das Event ursprünglich ausgelöst hat, was bei Event-Bubbling ein beliebiges Kindelement des registrierten Elements sein kann, und ist deshalb typischerweise als breiterer EventTarget typisiert, der weniger spezifische Properties bietet.
Für die meisten Event Handler Types in React ist currentTarget die richtige Wahl, weil es zuverlässig auf das Element mit der bekannten, erwarteten Struktur zeigt. Ein Klick-Handler an einem <button>, der über currentTarget.dataset auf ein Data-Attribut zugreift, bekommt vom Compiler exakt die Properties eines HTMLButtonElement vorgeschlagen. Greift man stattdessen auf target zu, weil das Event tatsächlich von einem verschachtelten <span> innerhalb des Buttons ausgelöst wurde, ist eine explizite Typumwandlung oder ein Typ-Guard nötig, um button-spezifische Properties sicher zu verwenden.
import type { MouseEvent } from "react";
function IconButton() {
const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
// currentTarget: always the button the handler is attached to
console.log(event.currentTarget.dataset.action);
// target: whichever element within the button actually triggered the event
// (could be an inner <svg> or <span>), typed more loosely as EventTarget
if (event.target instanceof HTMLElement) {
console.log(event.target.tagName);
}
};
return (
<button data-action="delete" onClick={handleClick}>
<span>Löschen</span>
</button>
);
}
6. Event-Handler als Props: MouseEventHandler und Co.
Wird ein Event-Handler nicht innerhalb der Komponente selbst definiert, sondern als Prop von außen übergeben, ist die einzeilige Handler-Typ-Familie mit dem Suffix Handler die kompaktere Alternative zur ausgeschriebenen Funktionssignatur. MouseEventHandler<HTMLButtonElement> ist funktional identisch zu (event: MouseEvent<HTMLButtonElement>) => void, liest sich aber deutlich kürzer in einem Props-Interface und macht auf den ersten Blick klar, dass es sich um einen Event-Handler und nicht um einen beliebigen Callback handelt.
Diese kompakte Schreibweise existiert für praktisch jeden gängigen Event-Typ: ChangeEventHandler, FormEventHandler, KeyboardEventHandler, FocusEventHandler. Alle folgen demselben Muster und erwarten denselben generischen Elementtyp wie ihr Event-Pendant. Für Props-Interfaces mit mehreren Event-Handlern verbessert diese Namenskonvention die Lesbarkeit erheblich, weil das Suffix Handler im Interface sofort erkennbar macht, um welche Art von Prop es sich handelt, ohne die vollständige Funktionssignatur lesen zu müssen.
import type { MouseEventHandler, ChangeEventHandler } from "react";
interface EditableRowProps {
onDelete: MouseEventHandler<HTMLButtonElement>;
onLabelChange: ChangeEventHandler<HTMLInputElement>;
label: string;
}
// Equivalent to: onDelete: (event: MouseEvent<HTMLButtonElement>) => void
function EditableRow({ onDelete, onLabelChange, label }: EditableRowProps) {
return (
<div>
<input value={label} onChange={onLabelChange} />
<button onClick={onDelete}>Löschen</button>
</div>
);
}
7. Keyboard- und Focus-Events typsicher behandeln
KeyboardEvent bringt neben den Basis-Properties von SyntheticEvent zusätzlich spezifische Felder wie key, code und die Modifier-Flags shiftKey, ctrlKey und altKey mit. Für Tastatur-Interaktionen, etwa das Bestätigen eines Formulars mit der Enter-Taste oder das Schließen eines Dialogs mit Escape, prüft man event.key gegen einen String, wobei der Compiler auch hier die exakten Property-Namen vorschlägt, statt sich auf veraltete, seit langem missbilligte Properties wie keyCode zu verlassen.
FocusEvent folgt demselben Muster für onFocus- und onBlur-Handler und ergänzt zusätzlich das Feld relatedTarget, das bei onBlur das Element beschreibt, zu dem der Fokus als Nächstes wechselt. Ein typischer Anwendungsfall ist ein Dropdown, das sich schließen soll, sobald der Fokus das Dropdown und seine Kindelemente vollständig verlässt, aber offen bleiben soll, solange relatedTarget noch innerhalb des Dropdown-Containers liegt.
import { useRef } from "react";
import type { KeyboardEvent, FocusEvent, ReactNode } from "react";
function SearchBox() {
// Keyboard event: "key" is a proper string, no deprecated keyCode needed
const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {
if (event.key === "Enter") {
console.log("Search confirmed");
}
if (event.key === "Escape") {
event.currentTarget.blur();
}
};
return <input type="text" onKeyDown={handleKeyDown} />;
}
function Dropdown({ children }: { children: ReactNode }) {
const containerRef = useRef<HTMLDivElement>(null);
// relatedTarget: the element focus is moving to, may be null
const handleBlur = (event: FocusEvent<HTMLDivElement>) => {
if (!containerRef.current?.contains(event.relatedTarget)) {
console.log("Focus left the dropdown entirely, closing it");
}
};
return (
<div ref={containerRef} onBlur={handleBlur} tabIndex={-1}>
{children}
</div>
);
}
8. Eigene Callback-Signaturen statt nativer Events
Nicht jeder Callback, den eine Komponente nach außen anbietet, sollte zwangsläufig ein natives SyntheticEvent weiterreichen. Eine Select-Komponente, die eine Option auswählt, oder eine Rating-Komponente, die einen Sternewert liefert, kommuniziert nach außen sinnvoller über eine eigene, domänenspezifische Callback-Signatur, etwa onSelect: (option: Product) => void, statt den internen ChangeEvent oder MouseEvent unverändert durchzureichen. Die aufrufende Komponente interessiert sich für das ausgewählte Produkt, nicht für Implementierungsdetails wie das zugrunde liegende native Element.
Diese Entkopplung hat einen praktischen Vorteil für Event Handler Types: Ändert sich intern, mit welchem HTML-Element eine Komponente eine Interaktion umsetzt, etwa der Wechsel von einem nativen <select> zu einer Liste aus <div>-Elementen für mehr Styling-Freiheit, bleibt die nach außen sichtbare Callback-Signatur davon vollständig unberührt. Die Komponente extrahiert intern die relevanten Daten aus dem jeweiligen nativen Event und reicht ausschließlich diese Daten über die eigene, stabile Signatur weiter.
interface Product { id: number; name: string; price: number; }
interface ProductPickerProps {
products: Product[];
// Domain-specific callback instead of a raw native event
onSelect: (product: Product) => void;
}
function ProductPicker({ products, onSelect }: ProductPickerProps) {
// Internal native event is translated into the domain callback here
const handleChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const selected = products.find((p) => String(p.id) === event.target.value);
if (selected) onSelect(selected);
};
return (
<select onChange={handleChange}>
{products.map((p) => (
<option key={p.id} value={p.id}>{p.name}</option>
))}
</select>
);
}
// Caller only ever sees a typed Product, never the underlying ChangeEvent
// <ProductPicker products={products} onSelect={(product) => console.log(product.price)} />
9. Event-Handler-Typisierung im Vergleich
Die folgende Übersicht fasst zusammen, welcher Ansatz zur Typisierung von Event Handlern für welches Szenario geeignet ist.
| Szenario | Unpassender Ansatz | Empfohlener Ansatz | Vorteil |
|---|---|---|---|
| Handler direkt in der Komponente | (event: any) => void |
(event: ChangeEvent<HTMLInputElement>) => void |
Autovervollständigung und geprüfte Properties |
| Handler als Prop von außen | ausgeschriebene Funktionssignatur | MouseEventHandler<HTMLButtonElement> |
Kompakter, sofort als Handler erkennbar |
| Zugriff auf das registrierte Element | event.target |
event.currentTarget |
Zuverlässig der Typ des registrierten Elements |
| Öffentliche Komponenten-API | natives Event unverändert durchreichen | eigene Callback-Signatur wie onSelect(item: T) |
Interne Implementierung bleibt austauschbar |
| Viele fast identische Feld-Handler | ein Handler pro Formularfeld | ein generischer Handler über name |
Deutlich weniger duplizierter Code |
Kein einzelner Ansatz ist universell richtig. Für einfache, lokale Handler reicht die konkrete Event-Typisierung völlig aus. Sobald ein Handler jedoch als Prop nach außen sichtbar wird, lohnt sich entweder die kompakte Handler-Typ-Familie oder, bei domänenspezifischen Komponenten, eine eigene Callback-Signatur, die native Event-Details bewusst verbirgt.
Mironsoft
TypeScript-Tooling, typsichere React-Formulare und Magento/Hyvä-Integrationen
Event-Handler ohne any, dafür mit voller Typprüfung?
Wir prüfen bestehende React-Komponenten auf lose typisierte Events, ersetzen any-Handler durch korrekte SyntheticEvent-Typen und bauen typsichere Formular- und Interaktionslogik für euer Frontend.
Event-Audit
Bestehende Handler auf any und lose Event-Typen prüfen
Formular-Refactoring
Generische, typsichere Handler für komplexe Formulare einführen
Komponenten-API
Domänenspezifische Callback-Signaturen statt roher Events
10. Zusammenfassung
Event Handler Types in React beginnen mit dem Verständnis, dass React eigene, aus dem react-Paket importierte Event-Typen bereitstellt, die sich von nativen DOM-Typen unterscheiden. MouseEvent, ChangeEvent und FormEvent decken zusammen mit ihrem generischen Elementtyp-Parameter die häufigsten Interaktionen ab, während die kompakte Handler-Typ-Familie sich für Event-Handler eignet, die als Props nach außen gereicht werden. currentTarget bleibt für die meisten Fälle die verlässlichere Wahl gegenüber target, weil es exakt den Typ des registrierten Elements trägt.
Für Formulare mit vielen Feldern reduziert ein generischer Handler über das name-Attribut Duplizierung erheblich, verlangt aber eine bewusste, sichtbare Typ-Assertion an einer einzigen Stelle. Öffentliche Komponenten-APIs profitieren davon, native Events durch eigene, domänenspezifische Callback-Signaturen zu ersetzen, damit interne Implementierungsdetails austauschbar bleiben, ohne die nach außen sichtbare Schnittstelle zu verändern. Wer diese Prinzipien konsequent anwendet, ersetzt any-Handler durch Code, der Tippfehler und falsche Property-Zugriffe bereits beim Kompilieren meldet.
Event Handler Types in React - Das Wichtigste auf einen Blick
SyntheticEvent
React-eigene Event-Typen aus dem react-Paket, nicht aus lib.dom.d.ts, mit generischem Elementtyp-Parameter.
currentTarget vs. target
currentTarget trägt zuverlässig den Typ des registrierten Elements, target ist bei Bubbling breiter typisiert.
Handler als Props
MouseEventHandler, ChangeEventHandler und Co. als kompakte Alternative zur ausgeschriebenen Funktionssignatur.
Öffentliche APIs
Domänenspezifische Callbacks statt roher Events, damit interne Implementierungsdetails austauschbar bleiben.