Das Schreiben von React Hooks: useState, useEffect, useReducer und benutzerdefinierte Hooks
Erfahren Sie, wie man useState, useEffect, useReducer sowie benutzerdefinierte Hooks in TypeScript korrekt verwendet – und wann sich die Wahl von TypeScript gegenüber reinem JavaScript tatsächlich lohnt.
Typfehler, die bereits zur Kompilierzeit erkannt werden, ersparen Ihnen Debugging-Sitzungen, die sonst bis spät in die Nacht andauern könnten. Genau deshalb betrachten Teams, die React mit TypeScript kombinieren, typisierte Hooks als Grundvoraussetzung und nicht als Luxus. Sich mit der Typisierung von State, Effects, Reducern und benutzerdefinierten Hooks vertraut zu machen – sowie zu wissen, wann der Einsatz von TypeScript von vornherein die richtige Entscheidung ist – sind zwei Seiten derselben praktischen Fähigkeitsausstattung für die Entwicklung zuverlässiger React-Anwendungen.
Warum Typsicherheit zu Ihren Hooks gehören sollte
React Hooks bieten Entwicklern bereits eine sauberere, komponentenbasierte Art und Weise, Komponenten zu strukturieren. TypeScript fügt zu dieser Struktur eine Sicherheitsschicht hinzu, sodass Fehler bereits während des Programmierens sichtbar werden und nicht erst dann, wenn ein Benutzer in der Produktion auf einen fehlerhaften Bildschirm stößt.
Hooks wurden von Anfang an so konzipiert, dass wiederverwendbare Logik vorhersehbar wird – ein Punkt, den Dan Abramov bei der Erörterung der Designphilosophie von React hervorgehoben hat. Der Beitrag von TypeScript besteht darin, diese Vorhersehbarkeit explizit zu machen und überprüfbar zu gestalten, anstatt dass man sie sich nur im Kopf merken muss.
useState korrekt typisieren
In den meisten alltäglichen Fällen erkennt TypeScript den Typ des States von selbst ohne jegliche Hilfe:
// inferred as boolean, no annotation needed
const [isLoading, setIsLoading] = useState(false);
Es ändert sich etwas, wenn der Anfangswert null oder undefined ist – in diesem Fall müssen Sie den Typ selbst annotieren, anstatt sich auf die Inferenz zu verlassen:
interface UserProfile {
id: string;
name: string;
avatarUrl: string;
}
const [user, setUser] = useState<UserProfile | null>(null);
Eine nützliche Gewohnheit ist es, dem Drang zu widerstehen, einfach auf einen generischen Typ zurückzugreifen, nur um eine Fehlermeldung verschwinden zu lassen. Dadurch geht der gesamte Nutzen, den Sie ursprünglich von TypeScript erwartet haben, verloren.
Typisierung von useEffect: Einfacher als erwartet
Generics gelten eigentlich nicht für useEffect, man muss jedoch trotzdem vorsichtig mit Abhängigkeitsarrays und Aufräumlogik sein:
useEffect(() => {
const controller = new AbortController();
const fetchUser = async () => {
const res = await fetch(`/api/users/${userId}`, {
signal: controller.signal,
});
const data: UserProfile = await res.json();
setUser(data);
};
fetchUser();
return () => controller.abort(); // cleanup on unmount
}, [userId]);
Typisierung von useReducer für komplexere Zustände
Dies ist der Hook, bei dem der Vorteil von TypeScript besonders deutlich wird:
type CartAction =
| { type: 'ADD_ITEM'; payload: CartItem }
| { type: 'REMOVE_ITEM'; payload: string }
| { type: 'CLEAR_CART' };
function cartReducer(state: CartItem[], action: CartAction): CartItem[] {
switch (action.type) {
case 'ADD_ITEM':
return [...state, action.payload];
case 'REMOVE_ITEM':
return state.filter((item) => item.id !== action.payload);
case 'CLEAR_CART':
return [];
default:
return state;
}
}
Durch diese Modellierung der Aktionstypen wird das Versenden ungültiger Daten zu einem Fehler zur Kompilierzeit, den man sofort erkennt, anstatt zu einem Überraschungseffekt zur Laufzeit, der erst später auffällt.
Schreibweise wiederverwendbarer Custom Hooks mit Generics
function useLocalStorage<T>(key: string, initialValue: T) {
const [value, setValue] = useState<T>(() => {
const stored = window.localStorage.getItem(key);
return stored ? JSON.parse(stored) : initialValue;
});
useEffect(() => {
window.localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
Weil dieser Hook generisch ist, kann er überall in Ihrer Codebasis wiederverwendet werden – mit Strings, Objekten oder Arrays – und behält dabei die volle Typgenauigkeit für die übergebenen Daten bei.
Kurzfassung
- Lassen Sie TypeScript einfachen Zustand eigenständig ableiten, seien Sie aber explizit, wenn ein Wert null oder undefined sein kann.
- Modellieren Sie die Actions Ihres useReducer als diskriminierte Union.
- Verwenden Sie Generics in benutzerdefinierten Hooks, um deren Wiederverwendbarkeit bei verschiedenen Datentypen zu gewährleisten.
- Betrachten Sie
anyals Abkürzung, die Ihnen später mehr kostet, als sie einspart.
Auswahl zwischen reinem JavaScript und TypeScript
Sobald Sie verstanden haben, wie viel Sicherheit typisierte Hooks bieten, stellt sich natürlich die breitere Frage: Soll jedes Projekt tatsächlich TypeScript verwenden – oder ist das manchmal übertriebene Ingenieurskunst? Lange Zeit wurde dies als binäre Wahl dargestellt, wobei die Entscheidung für eine Seite bedeutete, mit echten Kompromissen leben zu müssen.
In der Vergangenheit bedeutete die Wahl von TypeScript, sich mit Build-Pipelines, Tools wie ts-node sowie einer Menge an Konfigurationsdateien auseinanderzusetzen, nur um ein Skript zum Laufen zu bringen. Da nun Laufzeiten wie Node.js native Typenentfernung unterstützen, ist dieser Widerstand größtenteils verschwunden, und die Grenze zwischen der Verwendung von reinem JavaScript und TypeScript ist viel schmaler als früher.
Durch die Betrachtung, wie jede Option den täglichen Arbeitsablauf beeinflusst, fällt es leichter zu entscheiden, welche tatsächlich zum vorliegenden Projekt passt.
Warum reines JavaScript immer noch seinen Platz hat
JavaScript ist die Sprache, die vom Browser und Node.js nativ verstanden wird. Man schreibt eine Datei, führt sie aus – und sie wird sofort ausgeführt, ohne dass ein Compiler im Weg steht oder etwas Zusätzliches deklariert werden muss.
- Schnelles Prototyping: Wenn Sie eine Idee testen, einen schnellen Script schreiben oder ein kleines MVP erstellen, ermöglicht JavaScript es Ihnen, genauso schnell voranzukommen wie Ihr Denken.
- Keine Einrichtung erforderlich: Es ist weder ein
tsconfig.jsonnoch ein Schritt zur Typüberprüfung notwendig, um nur zu bestätigen, dass eine Funktion das tut, was man erwartet. - Kleinerer mentaler Aufwand: Ihre Aufmerksamkeit bleibt auf der eigentlichen Logik des Programms statt auf Typdeklarationen oder Compilerwarnungen.
Die Nachteile treten auf, sobald eine JavaScript-Codebasis mehrere tausend Zeilen umfasst. Ab diesem Punkt wird Refactoring riskant – das Umbenennen einer Eigenschaft in zwanzig Dateien bedeutet, auf eine globale Suche und Ersetzung angewiesen zu sein und darauf zu hoffen, dass nichts stillschweigend kaputtgeht, wenn die Anwendung läuft.
Warum TypeScript sich lohnt, wenn Projekte wachsen
TypeScript legt ein statisches Typsystem auf JavaScript auf und fungiert wie ein automatischer Überprüfer, der Probleme bereits während des Schreibens anzeigt – beispielsweise warnt er Sie sofort, wenn Sie versuchen, eine Zeichenkette an eine Funktion zu übergeben, die eine Zahl erwartet.
- Dokumentation, die stets aktuell bleibt: Typen dienen gleichzeitig als lebendige Dokumentation. Eine Schnittstelle gibt an, welche genaue Struktur ein Objekt haben muss, ohne dass Sie mehrere Implementierungsdateien durchsuchen müssen.
- Sicheres Refactoring: Wenn Sie ein Datenmodell oder ein Datenbankschema ändern, weist der Compiler auf alle Stellen im Codebasis hin, die nun aktualisiert werden müssen.
- Bessere Editor-Unterstützung: Tools wie VS Code oder Cursor nutzen den Language Server von TypeScript, um während der Arbeit eine schnelle Autocomplete-Funktion sowie die Hervorhebung von Fehlern direkt im Code anzubieten.
Historisch gesehen bestand der Kompromiss in einer echten „Tooling-Steuer“ – die Konfiguration von Compilern, Quellkarten und Wrapper-Skripten fügte zusätzliche Schritte zu einem einst einfachen Workflow hinzu.
Warum dieser Kompromiss nicht mehr auf dieselbe Weise gilt
Der alte Vorwurf, dass „die Einrichtung von TypeScript zu viel Aufwand ist“, hält heutzutage nicht mehr so stark stand. Dank des Typenentfernens können aktuelle Versionen von Node.js TypeScript-Dateien direkt ausführen, ohne einen separaten Build-Schritt.
Hinter den Kulissen entfernt die Laufzeit einfach Ihre Typangaben und Interface-Deklarationen, sodass reines JavaScript sofort ausgeführt werden kann. Das bedeutet, Sie behalten die Sicherheit statischer Typen während der Entwicklung ohne den damit einhergehenden Einrichtungsaufwand.
Das richtige Werkzeug für die Aufgabe wählen
Für ein einmalig verwendbares Skript, eine einfache Automatisierungsaufgabe oder ein Projekt, das ausschließlich dazu dient, zu lernen, wie das Web funktioniert, bleibt reines JavaScript die bessere Wahl. Das Vermeiden zusätzlicher Strukturen sorgt dafür, dass der Prozess unkompliziert und angenehm bleibt.
Für fast alles andere – Team-Codebasen, Anwendungen mit mehreren miteinander verbundenen Datenmodellen oder jedes Projekt, das länger als einen Monat gepflegt werden soll – lohnt sich die geringe Menge an Einrichtung, die TypeScript erfordert. Da moderne Laufzeiten in beiden Fällen eine reibungslose Ausführung ermöglichen, muss man eigentlich nicht zwischen Flexibilität und Sicherheit wählen: Sowohl die Einfachheit von JavaScript als auch die Schutzmechanismen von TypeScript sind verfügbar, und die Entscheidung dafür hängt hauptsächlich davon ab, wie langfristig und kooperativ das Projekt sein wird.
Verwandte Artikel
- TypeScript 6.0’s Breaking Defaults: Ein praktischer Migrationsleitfaden — Erfahren Sie, welche neun Compiler-Einstellungen in TypeScript 6.0 geändert wurden, wie Sie tsconfig für das Jahr 2026 konfigurieren können und wie Sie Codebasen auf den auf Go basierenden TypeScript 7 vorbereiten.
- Die Full-Stack-JavaScript-Einstellungen für 2026: TypeScript, RSC und mehr — Erklärt, warum TypeScript, React Server Components sowie ein effizienteres State-Management-Ansatz im Jahr 2026 zum Standard für JavaScript-Teams in der Produktion geworden sind.