Hooks mit TypeScript typisieren in React
Hooks mit TypeScript typisieren
~15 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
React-Hooks wie useState sind selbst GENERISCH – sie akzeptieren einen Typ-Parameter, der bestimmt, was der State enthalten darf. Meistens erkennt TypeScript diesen Typ automatisch, manchmal muss man nachhelfen.
useState: automatische Typinferenz aus dem Anfangswert
const [query, setQuery] = useState('');
// TypeScript leitet automatisch ab: query ist vom Typ 'string'
// setQuery(42) wäre jetzt ein Typfehler, ohne dass wir irgendetwas explizit hingeschrieben haben
const [count, setCount] = useState(0);
// automatisch: numberWenn der Anfangswert nicht reicht: explizite Typparameter
Bei null-Anfangswerten, die SPÄTER etwas anderes werden sollen, reicht Inferenz nicht – TypeScript würde null für immer als einzig möglichen Typ annehmen. Hier der typisierte AuthStore (Zustand statt Redux Toolkit, siehe Kapitel 28) als Beispiel:
import { create } from 'zustand';
interface User {
username: string;
}
interface AuthState {
user: User | null;
login: (username: string) => void;
logout: () => void;
}
export const useAuthStore = create<AuthState>((set) => ({
user: null,
login(username) {
set({ user: { username } });
},
logout() {
set({ user: null });
},
}));create<AuthState>((set) => ({{...}})) – der explizite Typparameter <AuthState> VOR den runden Klammern zwingt Zustand, den gesamten zurückgegebenen Store-Zustand gegen dieses Interface zu prüfen. User | null ("Union Type") beschreibt exakt unseren Anwendungsfall: entweder ein echter Benutzer ODER null, wenn niemand eingeloggt ist – jeder Code, der user.username OHNE vorherige Null-Prüfung liest, wird von TypeScript SOFORT als Fehler markiert.
useRef: zwei unterschiedliche Anwendungsfälle, zwei Typmuster
// Fall 1: Ref auf ein DOM-Element (wie searchInputRef in ProductListPage)
// Anfangswert MUSS null sein, Typ beschreibt das SPÄTERE DOM-Element:
const searchInputRef = useRef<HTMLInputElement>(null);
// Zugriff:
searchInputRef.current?.focus(); // ?. PFLICHT, da .current anfangs null ist
// Fall 2: Ref als reiner, veränderlicher Wertspeicher (wie renderCount in RenderCounter)
// Anfangswert ist direkt vom Zieltyp, .current ist NIE null:
const renderCount = useRef<number>(0);
renderCount.current += 1; // kein ?. nötig, .current ist immer eine numberuseContext: der Null-Guard-Pattern aus AuthContext
Falls Sie Context statt Zustand einsetzen wollten (siehe Kapitel 27/28 für die Grenzen-Diskussion) – hier zur Vollständigkeit das typisierte Muster, das "React für Einsteiger" bereits in JavaScript eingeführt hat:
interface AuthContextValue {
user: User | null;
login: (username: string) => void;
logout: () => void;
}
const AuthContext = createContext<AuthContextValue | null>(null);
export function useAuth(): AuthContextValue {
const context = useContext(AuthContext);
if (context === null) {
throw new Error('useAuth() must be used inside <AuthProvider>.');
}
return context; // TypeScript weiß hier: context kann NICHT mehr null sein
}Der if (context === null) throw ...-Check ist nicht nur ein Laufzeit-Sicherheitsnetz (wie schon in "React für Einsteiger" erklärt), sondern auch ein TypeScript-Feature namens "Type Narrowing": NACH diesem Check weiß TypeScript, dass context nur noch AuthContextValue sein kann, nicht mehr AuthContextValue | null – der Rückgabetyp AuthContextValue (ohne | null) der Funktion ist daher korrekt, ohne einen zusätzlichen Cast.
useSelector/useDispatch: die im letzten Chapter vorbereiteten Typen nutzen
Erinnern Sie sich an RootState/AppDispatch aus store/index.ts (Kapitel 40)? Jetzt kommen sie zum Einsatz – für TYPSICHERE Redux-Hooks empfiehlt Redux Toolkit, EIGENE, vorkonfigurierte Versionen von useSelector/useDispatch anzulegen:
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from './index';
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;Ab jetzt wird ÜBERALL im Projekt useAppSelector/useAppDispatch statt der "nackten" useSelector/useDispatch aus react-redux verwendet – der Selektor-Parameter (state in (state) => state.cart.items) bekommt dadurch automatisch den korrekten RootState-Typ, ohne dass Sie ihn jedes Mal manuell annotieren müssten:
// Vorher (in CartWidget.jsx, ohne Typsicherheit):
const itemCount = useSelector((state) => state.cart.items.length);
// Nachher (in CartWidget.tsx, state ist automatisch als RootState erkannt):
import { useAppSelector } from '../store/hooks';
const itemCount = useAppSelector((state) =>
state.cart.items.reduce((total, item) => total + item.quantity, 0)
);Tipp: import type {{ RootState, AppDispatch }} statt einem normalen import – das type-Schlüsselwort signalisiert explizit, dass NUR Typinformationen importiert werden, die beim Kompilieren komplett verschwinden (siehe "noEmit": true aus Kapitel 40) und zur LAUFZEIT keinerlei echten Code-Import verursachen.