Typen im Komponentenbau

Eigenschaften, Ereignisse und Hooks ohne Ratespiel

Wer die vier Standardmuster kennt, tippt keine Attributlisten mehr ab und bekommt beim Aufruf einer Komponente genau die Fehler gemeldet, die sonst erst im Browser auffallen.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Seit 1997 am Markt Kleine Gruppen Präsenz und Live-Online Zertifizierte Trainer
Worum es geht

Abgetippte Eigenschaftslisten veralten schneller, als sie geschrieben sind

Der typische Anfang: Eine eigene Schaltfläche bekommt einen Typ mit onClick, disabled und className. Zwei Wochen später fehlt type, dann aria-label, dann autoFocus. Jede dieser Ergänzungen ist eine Zeile, aber sie fällt erst auf, wenn jemand das Attribut benutzen will und der Compiler es ablehnt. Am Ende steht eine Liste, die einen Teil der HTML-Attribute nachbaut und dabei jede Änderung an der Vorlage verpasst.

Der zweite wiederkehrende Punkt sind Ereignisse. Wer einen Behandler direkt im JSX schreibt, bekommt die Typen geschenkt, weil React sie aus dem Element ableitet. Sobald derselbe Behandler als eigene Funktion daneben steht, ist das vorbei, und es beginnt die Suche nach dem passenden Typ. Mit einer falschen Wahl kompiliert es zwar, aber currentTarget hat plötzlich nicht mehr die Felder, die man erwartet.

Dazu kommt eine Reihe von Änderungen aus React 19, die in bestehenden Projekten Fehler erzeugen, die auf den ersten Blick nichts miteinander zu tun haben: useRef verlangt jetzt ein Argument, der globale JSX-Namensraum ist weg, forwardRef wird für neue Komponenten nicht mehr gebraucht, und einige alte Typen sind entfallen. Wer das nicht als Bündel erkennt, repariert es Datei für Datei.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt
Der Aufbau im Detail

Ein Eigenschaftstyp, Stück für Stück

Diese eine Zeile enthält vier der fünf Muster. Sie ist der Ausgangspunkt für fast jede eigene Komponente, die ein natives Element umschließt, von der Schaltfläche über das Eingabefeld bis zum Verweis.

Der Aufbau

type ButtonProps = ComponentPropsWithoutRef<"button"> & { variant: "primary" | "ghost"; children?: ReactNode }
  1. 01 Ein eigener Typ statt React.FC type ButtonProps

    Die Eigenschaften bekommen einen benannten Typ, die Komponente bleibt eine normale Funktion. React.FC ist aus der Mode gekommen, weil es früher stillschweigend children ergänzte, mit generischen Komponenten schlecht zusammenarbeitet und keinen Vorteil bietet, den ein benannter Typ nicht auch hätte.

  2. 02 Native Attribute erben ComponentPropsWithoutRef<"button">

    Liefert alle Attribute des HTML-Elements, einschließlich Ereignissen, ARIA-Attributen und data-Attributen. Für eine Komponente, die einen Verweis rendert, steht dort a, für ein Eingabefeld input. Die Variante ohne ref ist die richtige, wenn du ref selbst als Eigenschaft entgegennimmst oder gar nicht durchreichst.

  3. 03 Schnittmenge statt Vererbung &

    Verbindet die geerbten Attribute mit deinen eigenen. Wenn du einen Namen doppelt vergibst, entsteht dabei kein Fehler, sondern die Schnittmenge beider Typen, und die kann leer sein. Bei Namen wie type oder size lohnt es sich deshalb, das native Attribut vorher mit Omit auszuschließen.

  4. 04 Vereinigungstyp statt string "primary" | "ghost"

    Beschränkt den Wert auf die tatsächlich unterstützten Varianten. Der Editor schlägt sie vor, ein Tippfehler wird beim Aufruf gemeldet, und wenn du im Rumpf über alle Fälle verzweigst, meldet der Compiler beim Hinzufügen einer neuen Variante alle Stellen, die sie noch nicht behandeln.

  5. 05 Inhalt ausdrücklich deklarieren children?: ReactNode

    Seit den Typen für React 18 kommt children nicht mehr automatisch dazu. ReactNode ist dabei der richtige Typ, weil er alles abdeckt, was gerendert werden kann, also auch Zeichenketten, Zahlen, Listen und null. ReactElement wäre zu eng und würde einen einfachen Text ablehnen.

Wenn es nicht funktioniert

Das siehst du

useRef ohne Argument wird als Fehler gemeldet, obwohl der Code jahrelang so aussah.

Warum

Mit den Typen für React 19 verlangt useRef ein Argument, und alle Referenzen sind veränderbar. Der frühere Sonderfall ohne Argument ist entfallen.

Was hilft

Schreib den Anfangswert hin, also null für eine Referenz auf ein Element und undefined für einen veränderlichen Wert. Für ganze Projekte erledigt das ein Umbauwerkzeug in einem Durchlauf.

Das siehst du

Die Deklaration eines eigenen HTML-Elements wird nicht mehr gefunden.

Warum

Der globale JSX-Namensraum ist entfallen. Erweiterungen müssen jetzt innerhalb des react-Moduls deklariert werden.

Was hilft

Die Erweiterung in einen declare-module-Block für react verschieben. Welches Modul genau gemeint ist, hängt von der jsx-Einstellung in der tsconfig ab.

Das siehst du

Ein eigener Behandler passt nicht zu onChange, obwohl er im JSX geschrieben funktioniert hätte.

Warum

Im JSX leitet React den Typ aus dem Element ab. Eine daneben stehende Funktion hat diesen Zusammenhang nicht und braucht den Typ ausdrücklich.

Was hilft

Das Ereignis mit dem passenden React-Typ und dem konkreten Element annotieren. Verwende dabei nicht den globalen DOM-Typ, denn React reicht ein eigenes Objekt durch, dessen currentTarget bereits richtig typisiert ist.

Das siehst du

Eine generische Komponente in einer .tsx-Datei wird als JSX gelesen und erzeugt Syntaxfehler.

Warum

Eine spitze Klammer direkt vor einer Pfeilfunktion sieht in einer TSX-Datei aus wie ein öffnendes Element.

Was hilft

Schreib ein Komma hinter den Typparameter oder verwende eine normale Funktionsdeklaration. Beides ist eindeutig und wird vom Parser nicht als Element gelesen.

Das siehst du

Eine Komponente akzeptiert ref nicht, obwohl forwardRef gar nicht mehr nötig sein sollte.

Warum

In React 19 ist ref eine normale Eigenschaft, aber der Eigenschaftstyp muss sie auch enthalten. Bei ComponentPropsWithoutRef fehlt sie ausdrücklich.

Was hilft

Entweder ComponentPropsWithRef verwenden oder ref selbst als Eigenschaft mit dem passenden Referenztyp deklarieren. forwardRef bleibt nur für Bibliotheken nötig, die ältere React-Versionen unterstützen.

Vier Muster decken den Alltag ab

  1. 01 Eigenschaften: native Attribute erben und eigene per Schnittmenge ergänzen.
  2. 02 Inhalt: children als ReactNode deklarieren, nichts wird mehr stillschweigend ergänzt.
  3. 03 Ereignisse: den React-Typ passend zum Element wählen, nicht den globalen DOM-Typ.
  4. 04 Varianten: sich ausschließende Eigenschaften als Vereinigung mit Unterscheidungsfeld.
  5. 05 Rückgaben: eigene Hooks liefern unterscheidbare Zustände statt lauter optionaler Felder.
Was du mitnimmst

Was du nach diesen Mustern nicht mehr nachschlagen musst

Es sind vier Situationen, die zusammen den Großteil der Typarbeit in einem React-Projekt ausmachen. Wenn sie einmal sitzen, verschwindet auch der Reflex, im Zweifel any zu schreiben, weil die richtige Lösung schneller ist als die Ausrede.

Native Attribute erben statt abschreiben

ComponentPropsWithoutRef mit dem Namen des Elements liefert alle Attribute inklusive Ereignissen und ARIA. Deine eigenen Eigenschaften kommen per Schnittmenge dazu, und neue HTML-Attribute stehen automatisch zur Verfügung.

children ausdrücklich deklarieren

Seit den Typen für React 18 gibt es keine stillschweigenden children mehr. Wenn deine Komponente Inhalt aufnimmt, gehört children mit dem Typ ReactNode in die Eigenschaften, und zwar sichtbar.

Das richtige Ereignisobjekt wählen

React bringt eigene Typen mit, die nach Elementtyp unterscheiden. Für ein Eingabefeld ist es ein Änderungsereignis mit dem Eingabeelement, für ein Formular ein Absendeereignis mit dem Formularelement. Damit stimmt auch currentTarget.

Unmögliche Kombinationen ausschließen

Wenn zwei Eigenschaften sich gegenseitig ausschließen, beschreibst du das als Vereinigung zweier Varianten mit einem unterscheidenden Feld. Der Compiler lehnt die verbotene Kombination dann beim Aufruf ab, statt sie zur Laufzeit zu ignorieren.

Eigene Hooks mit unterscheidbaren Zuständen zurückgeben

Statt eines Objekts, in dem Daten, Fehler und Ladezustand alle optional sind, gib eine Vereinigung zurück. Die aufrufende Stelle prüft ein Feld, und danach weiß der Compiler, welche Felder vorhanden sind.

React-19-Änderungen als Bündel abarbeiten

useRef braucht ein Argument, der JSX-Namensraum ist umgezogen, ref ist eine normale Eigenschaft. Für die Umstellung gibt es ein fertiges Umbauwerkzeug, das die meisten Stellen automatisch anpasst.

KI-generiertDieses Bild wurde mit KI erzeugt · Yves Hoppe / KI / cmt

Eigenschaften beschreiben, ohne HTML nachzubauen

Für eine Komponente, die ein natives Element umschließt, ist ComponentPropsWithoutRef mit dem Elementnamen der Ausgangspunkt. Damit erbt deine Schaltfläche alles, was eine Schaltfläche kann, und du ergänzt nur, was zusätzlich gilt. Wenn du die Eigenschaften einer anderen Komponente brauchst, liefert ComponentProps mit typeof genau das, was diese Komponente entgegennimmt, was besonders beim Umschließen fremder Bausteine hilft.

Zwei Fallstricke tauchen dabei regelmäßig auf. Der erste sind Namenskollisionen: Wenn du eine eigene Eigenschaft size nennst und das native Element bereits ein size-Attribut hat, entsteht die Schnittmenge beider Typen, und die kann unbenutzbar werden. Der saubere Weg ist, das native Attribut mit Omit auszuschließen und dann das eigene zu definieren. Der zweite ist ref: Die Variante ohne ref ist richtig, solange du ref nicht weitergibst, und die Variante mit ref, sobald du es tust.

Für Bedienelemente mit mehreren Erscheinungsformen lohnt sich eine Vereinigung statt vieler optionaler Felder. Eine Komponente, die entweder einen Verweis oder eine Schaltfläche rendert, bekommt zwei Varianten mit einem unterscheidenden Feld, und jede Variante trägt genau die Eigenschaften, die in ihr erlaubt sind. Damit wird die Kombination aus href und onClick, die niemand haben will, beim Aufruf abgelehnt statt im Rumpf ignoriert.

Ereignisse: welcher Typ wann

React reicht kein natives DOM-Ereignis durch, sondern ein eigenes Objekt, das sich weitgehend gleich verhält und über alle Browser hinweg einheitlich ist. Deshalb kommen die Typen aus React und nicht aus der globalen Umgebung. Für ein Eingabefeld ist es ChangeEvent mit HTMLInputElement, für ein Formular FormEvent mit HTMLFormElement, für eine Schaltfläche MouseEvent mit HTMLButtonElement. Das native Objekt bleibt über nativeEvent erreichbar, wenn du es wirklich brauchst.

Der wichtigste Grund, den Elementtyp mit anzugeben, ist currentTarget. Nur damit weiß der Compiler, dass dort ein value existiert oder dass ein Formular vorliegt, aus dem sich FormData bauen lässt. target ist bewusst weiter gefasst, weil dort auch ein Kindelement stehen kann. Wer diese beiden verwechselt, greift auf Felder zu, die es an dieser Stelle nicht geben muss.

Für den Behandler selbst gibt es zwei gleichwertige Wege. Entweder du annotierst den Parameter, dann steht der Typ direkt an der Funktion, oder du typisierst die ganze Funktion, etwa als ChangeEventHandler mit dem Elementtyp, und lässt den Parameter daraus folgen. Der zweite Weg ist kürzer, wenn dieselbe Signatur mehrfach vorkommt, und er passt besser, wenn der Behandler als Eigenschaft weitergereicht wird.

Was React 19 an den Typen geändert hat

Vier Punkte betreffen bestehende Projekte spürbar. useRef verlangt jetzt ein Argument, und es gibt nur noch einen Referenztyp statt der früheren Unterscheidung zwischen veränderbar und nicht veränderbar. Der globale JSX-Namensraum ist entfallen, Erweiterungen für eigene Elemente werden innerhalb des react-Moduls deklariert. Bei ReactElement sind die Eigenschaften ohne Typargument nicht mehr any, sondern unknown, was an genau den Stellen auffällt, an denen jemand vorher blind darauf zugegriffen hat. Und eine Reihe alter Typen ist verschwunden oder abgekündigt: Die Typen rund um PropTypes gibt es nicht mehr, ElementRef ist zugunsten von ComponentRef abgekündigt und läuft vorerst weiter.

Der fünfte Punkt ist der angenehmste: ref ist eine normale Eigenschaft geworden. Neue Komponenten brauchen forwardRef nicht mehr, sie nehmen ref einfach in ihren Eigenschaften entgegen. Für Bibliotheken, die ältere React-Versionen unterstützen müssen, bleibt forwardRef bestehen, für Anwendungscode ist es Vergangenheit. Ein Detail dazu: Eine Rückruffunktion als ref darf nichts zurückgeben, weshalb die kurze Schreibweise mit einer Zuweisung als Ausdruck jetzt bemängelt wird.

Für die Umstellung gibt es ein fertiges Umbauwerkzeug für die Typen, das in einem Durchlauf den größten Teil erledigt. Sinnvoll ist die Reihenfolge: erst die Typpakete aktualisieren, dann das Werkzeug laufen lassen, dann die verbliebenen Meldungen durchsehen. Der Rest sind meist Stellen, an denen ein Typ bewusst zu weit gefasst war und jetzt auffällt.

Eigene Hooks mit klarer Rückgabe

Die Rückgabe entscheidet darüber, wie angenehm ein Hook zu benutzen ist. Ein Objekt mit benannten Feldern ist die richtige Wahl, sobald mehr als zwei Werte zurückkommen, weil die aufrufende Stelle dann nicht auf die Reihenfolge achten muss. Ein Tupel wie bei useState lohnt sich nur, wenn genau zwei Werte zurückkommen und die aufrufende Stelle sie oft umbenennt. Damit TypeScript daraus ein Tupel und kein Array ableitet, braucht die Rückgabe ein as const.

Der größte Gewinn liegt in unterscheidbaren Zuständen. Statt Daten, Fehler und Ladezustand alle als optional zu deklarieren, gib eine Vereinigung zurück: einen Ladezustand ohne Daten, einen Fehlerzustand mit Fehler und einen Erfolgszustand mit garantierten Daten. Nach einer Prüfung auf das unterscheidende Feld weiß der Compiler, welche Felder existieren, und in den Komponenten verschwinden die Fragezeichen und Ausrufezeichen, mit denen sonst über die Unsicherheit hinweggegangen wird.

Achte außerdem auf Stabilität. Wenn dein Hook bei jedem Durchlauf ein neues Objekt oder eine neue Funktion zurückgibt, vererbt er diese Instabilität an jede Komponente, die ihn benutzt, und deren Effekte und Merkfunktionen laufen entsprechend zu oft. Für Werte, die aus einer Quelle außerhalb von React kommen, ist useSyncExternalStore die vorgesehene Anbindung, weil sie den Wert beim Rendern konsistent liefert.

Generische Komponenten und fremde Typen

Eine Liste oder Tabelle, die mit beliebigen Daten arbeitet, wird generisch: Die Komponente nimmt einen Typparameter für den Eintrag entgegen, die Eigenschaft items ist ein Array davon, und die Funktion zum Rendern einer Zeile bekommt genau diesen Typ. Der Gewinn zeigt sich beim Aufruf, denn der Compiler leitet den Typ aus den übergebenen Daten ab und meldet jeden Feldnamen, den es dort nicht gibt. Für Spaltenangaben ist keyof der passende Baustein, damit nur echte Felder als Schlüssel zugelassen sind.

In einer TSX-Datei gibt es dabei eine Stolperfalle: Ein Typparameter direkt an einer Pfeilfunktion wird als öffnendes Element gelesen. Ein zusätzliches Komma nach dem Parameter oder eine gewöhnliche Funktionsdeklaration löst das. Diese Eigenheit kostet regelmäßig zehn Minuten, weil die Fehlermeldung von einem nicht geschlossenen Element spricht und nicht von Generik.

Bei fremden Komponenten ohne mitgelieferte Typen gibt es zwei saubere Wege. Entweder du schreibst eine eigene Deklarationsdatei für genau die Teile, die du benutzt, oder du erweiterst vorhandene Typen über Moduldeklaration, etwa um eine Eigenschaft, die eine Bibliothek in einer Erweiterung ergänzt. Beides ist mehr Aufwand als ein any an dieser Stelle und dafür die einzige Variante, die beim nächsten Aktualisieren der Bibliothek noch etwas wert ist.

Dazu passende Kurse

Wenn du das an einer eigenen Komponentenbibliothek üben willst, kannst du bei cmt Eigenschaften von Komponenten typsicher beschreiben .

Die Bausteine dahinter gehören zur Sprache selbst, und wer sie sauber kennt, spart sich das Ratespiel: Vereinigungen, Generik und Einengung sicher beherrschen .

Wissen prüfen

Wie sicher bist du beim Thema wirklich?

Lesen fühlt sich schnell nach Können an. Ein kurzer Test zeigt dir, was davon schon sitzt und wo sich ein Kurs lohnt. Kostenlos, ohne Anmeldung, mit einer Erklärung zu jeder Antwort.

Der passende Lernpfad

Wenn du nicht nur ein Thema abhaken, sondern eine Rolle ausfüllen willst, zeigt dir der Lernpfad die Kurse in der Reihenfolge, in der sie aufeinander aufbauen.

Karrierepfad

Dein Weg zum React Entwickler

Moderne Web-Produkte werden heute mit React gebaut. Wer es sauber beherrscht, arbeitet an echten Web-Anwendungen – nicht an Spielwiesen.
 

Dieser Karrierepfad führt dich gezielt von den Grundlagen über Architektur und Design bis hin zu professionellen React-Frontends, wie sie in produktiven Teams erwartet werden. Starte einfach durch!

Ein sehr gutes, praxisorientiertes und nachhaltiges Seminar. So sollte es immer sein. Vielen Dank.
LimeSurvey - Anwendertraining Teil 1 (Grundlagen)
Die Schulung ist gerade für Einsteiger oder Entwickler*innen mit eingestaubtem Basiswissen sehr hilfreich.
Vue.js 3 Grundkurs
Ich bin sehr glücklich Franz als Trainer gehabt zu haben. Er hat offensichtlich unglaublich viel Wissen zu den Themen, und schafft es, dieses auch interaktiv und verständlich weiterzugeben.
React Komplettausbildung

Häufige Fragen

Soll ich React.FC noch verwenden?
Es gibt keinen zwingenden Grund dagegen, aber auch keinen dafür. Der frühere Vorteil, dass children automatisch enthalten waren, ist mit den Typen für React 18 entfallen, und bei generischen Komponenten steht die Schreibweise im Weg. Ein benannter Eigenschaftstyp an einer normalen Funktion ist kürzer und in jeder Situation brauchbar.
interface oder type für Eigenschaften?
Für die meisten Fälle ist es Geschmackssache, und beides lässt sich mischen. Ein Unterschied zählt in der Praxis: Vereinigungen, wie du sie für sich ausschließende Eigenschaften brauchst, gehen nur mit type. interface hat dafür die nachträgliche Erweiterbarkeit, was für Bibliotheken wichtig ist, in Anwendungscode aber selten gebraucht wird.
Wie typisiere ich eine Komponente, die entweder einen Verweis oder eine Schaltfläche rendert?
Als Vereinigung zweier Varianten mit einem unterscheidenden Feld, etwa as mit den Werten link und button. Jede Variante erbt die nativen Attribute des jeweiligen Elements und enthält nur die dazu passenden eigenen Eigenschaften. Der Compiler lehnt dann beim Aufruf die Kombination aus href und einem reinen Schaltflächenattribut ab.
Wann ist unknown besser als any?
Fast immer, wenn Daten von außen kommen. any schaltet die Prüfung ab und lässt jeden Zugriff zu, unknown erzwingt eine Einengung, bevor du damit arbeitest. Für Antworten aus einer Schnittstelle ist unknown mit anschließender Prüfung die richtige Kombination, weil du danach einen Typ hast, der auch zur Laufzeit stimmt.
Persönlich für dich da

Deine Ansprechpartner

Du bist dir nicht sicher, welcher Kurs oder welches Level zu dir passt? Wir beraten dich persönlich und kostenlos.

Yves Hoppe

Yves Hoppe

Weiterbildung & Beratung

Hilft dir, aus dem Webentwicklung-Programm den passenden Kurs für deinen Stand zu finden.

Norbert Jansen

Norbert Jansen

Beratung & Inhouse

Plant mit dir Inhouse-Trainings, die auf eure Abläufe und euren Datenbestand zugeschnitten sind.

Typen, die beim Aufruf helfen statt zu bremsen

Bei cmt baust du diese Muster an eigenen Komponenten auf, von der geerbten Attributliste bis zur generischen Tabelle mit geprüften Spaltenschlüsseln.