Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

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: number

Wenn 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:

src/store/authStore.ts
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 number

useContext: 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:

src/store/hooks.ts
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.