7.6 React-Grundlagen: Die Hook-Familie
Nach dieser Einheit kannst du:
- Seiteneffekte mit
useEffectumsetzen, mit Dependency-Array und Cleanup, und die Endlosschleifen-Falle vermeiden. useReffür DOM-Zugriff und unsichtbare Merkwerte einsetzen und von State abgrenzen.- die Einsatzgebiete von
useContext,useMemounduseCallbacknennen und in fremdem Code erkennen. - wiederverwendbare Zustandslogik in einen eigenen Hook auslagern.
Ein Werkzeugkoffer hat für jede Aufgabe ein passendes Werkzeug: den Hammer für Nägel, den Schraubenzieher für Schrauben, den Zollstock zum Messen. Wer alles mit dem Hammer erledigt, bekommt die Schraube auch irgendwie in die Wand, aber nicht gut. Die Hooks von React sind so ein Koffer: useState hast du in Einheit 7.4 kennengelernt, useRef in 7.5 kurz gesehen. Diese Einheit packt den Rest aus und sagt zu jedem Werkzeug, wofür es gedacht ist und wofür nicht.
flowchart TD
accTitle: Die Hook-Familie und ihre Aufgaben
hooks["Hooks"] --> s["useState<br/>sichtbarer Zustand"]
hooks --> e["useEffect<br/>Kontakt zur Außenwelt"]
hooks --> r["useRef<br/>DOM und unsichtbare Werte"]
hooks --> c["useContext<br/>Werte ohne Prop-Treppe"]
hooks --> m["useMemo, useCallback<br/>Optimierung"]
hooks --> own["eigene Hooks<br/>wiederverwendbare Logik"]
Abb. 7.23: Jeder Hook hat eine Aufgabe; wer den passenden nicht benennen kann, fragt zuerst, ob es überhaupt Zustand ist (eigene Darstellung).
Für die ganze Familie gelten die zwei Regeln aus Einheit 7.4: Aufruf nur auf der obersten Ebene einer Komponente und nur in Komponenten oder in eigenen Hooks.
Begriffe vorab
Abschnitt betitelt „Begriffe vorab“- Seiteneffekt: alles, was beim Rendern über “Props und State hinein, JSX heraus” hinausgeht, etwa Daten laden oder einen Timer starten.
- Dependency-Array: die Liste von Werten, bei deren Änderung ein Effekt erneut läuft.
- Cleanup: die Aufräumfunktion eines Effekts, bevor er erneut läuft oder die Komponente verschwindet.
- Prop Drilling: einen Wert durch viele Ebenen Props reichen, die ihn selbst nicht brauchen.
- Memoisieren: ein Ergebnis aufheben und wiederverwenden, solange sich seine Eingaben nicht ändern.
useEffect: Seiteneffekte
Abschnitt betitelt „useEffect: Seiteneffekte“Rendern soll rein sein: Props und State hinein, JSX heraus. Alles, was darüber hinausgeht (Daten laden, Timer starten, den Dokumenttitel setzen), ist ein Seiteneffekt und gehört in useEffect. Im Beispiel holt loadJob einen Auftrag vom Server, etwa per fetch wie in Kapitel 5:
function JobMonitor({ jobId }: { jobId: number }) { const [job, setJob] = useState<Job | null>(null);
useEffect(() => { let canceled = false; loadJob(jobId).then((loaded) => { if (!canceled) setJob(loaded); // ignore stale responses }); return () => { canceled = true; }; // cleanup on unmount / re-run }, [jobId]); // re-run when jobId changes
if (job === null) return <p>loading ...</p>; return <JobCard filename={job.filename} progress={job.progress} />;}Beispielprojekt: 07_react_grundlagen_examples/ex19_job_monitor
Die drei Bestandteile:
- die Effekt-Funktion: Sie läuft nach dem Rendern.
- das Dependency-Array: Der Effekt läuft erneut, wenn sich einer der aufgezählten Werte ändert;
[]bedeutet nur einmal nach dem ersten Rendern. - die optionale Cleanup-Funktion: Sie räumt auf, bevor der Effekt erneut läuft oder die Komponente verschwindet. Hier entschärft sie die Race Condition aus Kapitel 4: Wechselt
jobIdschneller, als der Server antwortet, wird die überholte Antwort ignoriert.
sequenceDiagram
accTitle: Effekt und Cleanup bei wechselnder jobId
participant comp as JobMonitor
participant eff as Effekt
comp->>eff: Rendern mit jobId 1, Effekt startet loadJob(1)
comp->>eff: Rendern mit jobId 2
eff-->>eff: Cleanup: canceled = true für Job 1
eff->>eff: Effekt startet loadJob(2)
Note over eff: Antwort zu Job 1 kommt spät und wird ignoriert
Abb. 7.24: Vor jedem neuen Lauf räumt der alte Effekt auf; überholte Antworten bleiben folgenlos (eigene Darstellung).
Im Entwicklungsmodus führt StrictMode jeden Effekt einmal zusätzlich aus und räumt dazwischen auf. Damit testet React, ob dein Cleanup wirklich alles rückgängig macht; im Build passiert das nicht.
Zwei Warnungen aus der Praxis. Erstens: Ein fehlendes Dependency-Array plus Setter im Effekt ergibt eine Endlosschleife aus Rendern und Effekt. Zweitens: useEffect ist kein Werkzeug für abgeleitete Werte. Was sich aus Props und State ausrechnen lässt (gefilterte Liste, Summe), wird direkt beim Rendern berechnet, ohne Effekt und ohne eigenen State.
Auflösung
(a) Endlosschleife: Der Effekt läuft nach jedem Rendern, der Setter löst ein neues Rendern aus, und so weiter. (b) Der Effekt läuft einmal nach dem ersten Rendern, count wird 1 und bleibt dort. (c) Ebenfalls eine Endlosschleife: Jede Änderung von count startet den Effekt neu, der count wieder ändert.
useRef: die Kiste, die kein Rendern auslöst
Abschnitt betitelt „useRef: die Kiste, die kein Rendern auslöst“useRef liefert ein Objekt mit einer beschreibbaren .current-Eigenschaft, das über alle Renderdurchgänge dasselbe bleibt. Zwei Einsatzfälle:
- der Zugriff auf DOM-Elemente, wie beim uncontrolled Feld in Einheit 7.5 oder für
inputRef.current?.focus(); - das Aufbewahren veränderlicher Werte, die die Anzeige nicht betreffen, etwa eine Timer-ID oder ein Merker gegen Doppelklicks.
Eine Änderung an .current löst kein Rendern aus; genau das unterscheidet die Ref vom State.
useContext: Werte ohne Prop-Treppe
Abschnitt betitelt „useContext: Werte ohne Prop-Treppe“Manche Werte braucht fast jede Komponente: das Farbschema, die angemeldete Benutzerin. Sie durch fünf Ebenen Props zu reichen (Prop Drilling) macht alle Zwischenkomponenten zu Durchreichern:
// theme is only needed in ThemeBadge, but every level has to pass it along<App theme="dark"> <Page theme={theme}> {/* just forwards */} <Sidebar theme={theme}> {/* just forwards */} <ThemeBadge theme={theme} /> {/* finally uses it */}Das Problem ist nicht die Menge Code, sondern die Kopplung: Page und Sidebar müssen von einem Prop wissen, das sie nichts angeht, und jede neue Zwischenebene muss es ebenfalls durchschleifen. Ein Context stellt einen Wert allen Nachkommen direkt zur Verfügung:
const ThemeContext = createContext<"light" | "dark">("light");
function App() { return ( <ThemeContext value="dark"> <Toolbar /> {/* anywhere below ... */} </ThemeContext> );}
function ThemeBadge() { const theme = useContext(ThemeContext); // ... read it directly return <span>{theme}</span>;}Beispielprojekt: 07_react_grundlagen_examples/ex20_theme_context
In älterem Code steht statt <ThemeContext value="dark"> noch <ThemeContext.Provider value="dark">; das ist die Schreibweise vor React 19 und funktioniert weiterhin.
flowchart LR
accTitle: Prop Drilling gegen Context
subgraph drill["Prop Drilling"]
direction TB
a1["App"] -- "theme" --> p1["Page"]
p1 -- "theme" --> s1["Sidebar"]
s1 -- "theme" --> t1["ThemeBadge"]
end
subgraph ctx["Context"]
direction TB
a2["App<br/>ThemeContext"] --> p2["Page"]
p2 --> s2["Sidebar"]
s2 --> t2["ThemeBadge"]
a2 -. "useContext" .-> t2
end
Abb. 7.25: Mit Context wissen die Zwischenebenen nichts vom Wert (eigene Darstellung).
Context ist für querschnittliche, selten wechselnde Werte gedacht. Er ist kein Ersatz für gewöhnliche Props: Wer jeden Zustand global macht, verliert die Nachvollziehbarkeit des Datenflusses.
Wo begegnet dir das? Beim Dunkelmodus vieler Webanwendungen: Ein einziger Schalter in den Einstellungen färbt Kopfzeile, Menüs, Karten und Dialoge um, ohne dass jede Komponente dazwischen davon wissen muss.
useMemo und useCallback: Rechenarbeit aufheben
Abschnitt betitelt „useMemo und useCallback: Rechenarbeit aufheben“Jedes Mal, wenn React eine Komponente neu rendert, läuft ihre Funktion komplett von oben durch. Meistens ist das gleichgültig, weil React schnell ist. Es wird erst zum Problem, wenn eine Berechnung wirklich teuer ist (etwa das Filtern und Sortieren tausender Einträge bei jedem Tastendruck) oder wenn eine im Rumpf erzeugte Funktion als Prop an eine Kindkomponente geht, die nur bei echten Änderungen neu rendern soll. Für diese beiden Fälle gibt es je ein Werkzeug, das ein Ergebnis über mehrere Renderdurchgänge aufhebt (memoisiert):
useMemofür die teure Berechnung: Es merkt sich das Ergebnis der Funktion, bis sich eine Abhängigkeit ändert.useCallbackfür die stabile Funktion: Es merkt sich die Funktion selbst, sodass Kindkomponenten bei jedem Rendern dieselbe Referenz sehen.
// useMemo: skip the expensive computation unless `jobs` changedconst failedJobs = useMemo(() => jobs.filter((j) => j.state === "failed"), [jobs]);
// useCallback: hand the child the same function reference across rendersconst handleCancel = useCallback( (id: number) => setJobs((current) => current.filter((j) => j.id !== id)), [],);Beispielprojekt: 07_react_grundlagen_examples/ex21_memo_callback
Beide bekommen eine Funktion und ein Dependency-Array; der Unterschied ist nur, ob das Aufgehobene das Ergebnis der Funktion ist (useMemo) oder die Funktion selbst (useCallback). Technisch ist useCallback(fn, deps) nichts anderes als die Kurzform für useMemo(() => fn, deps). Das leere Array bei useCallback ist hier erlaubt, weil die Funktionsform des Setters den aktuellen Stand selbst bekommt.
Wichtig bleibt die Reihenfolge: Beide sind Optimierungen, und der Standardfall ist, sie nicht zu brauchen. Du greifst zu ihnen, wenn ein konkreter Engpass messbar ist, nicht vorsorglich; vorschnelle Memoisierung macht Code komplizierter statt schneller. Ins Repertoire gehören sie trotzdem, damit du sie in fremdem Code liest.
Eigene Hooks: Logik wiederverwenden
Abschnitt betitelt „Eigene Hooks: Logik wiederverwenden“Wiederkehrende Zustandslogik lässt sich in eigene Hooks auslagern: gewöhnliche Funktionen mit use-Präfix, die andere Hooks verwenden. Ein Klassiker, der Eingaben über ein Neuladen der Seite hinweg erhält:
function useLocalStorage(key: string, initial: string) { const [value, setValue] = useState( () => localStorage.getItem(key) ?? initial);
useEffect(() => { localStorage.setItem(key, value); }, [key, value]);
return [value, setValue] as const;}
// in any component, same shape as useState:const [draft, setDraft] = useLocalStorage("comment-draft", "");Beispielprojekt: 07_react_grundlagen_examples/ex22_use_local_storage
Drei Details: useState(() => ...) ist die Lazy-Form, bei der React die Funktion nur beim ersten Rendern aufruft, statt bei jedem Durchgang aus dem Speicher zu lesen. localStorage speichert nur Text, deshalb arbeitet der Hook mit Strings. Und as const sorgt dafür, dass TypeScript das Ergebnis als Paar [string, Setter] kennt statt als Array gemischter Typen.
flowchart LR
accTitle: Ein eigener Hook kapselt State und Effekt
comp["Komponente<br/>useLocalStorage(...)"] --> hook["useLocalStorage"]
hook --> st["useState<br/>Wert"]
hook --> ef["useEffect<br/>speichern"]
ef --> ls[("localStorage")]
ls -. "beim ersten Rendern lesen" .-> st
Abb. 7.26: Die Komponente sieht nur ein Paar aus Wert und Setter; Laden und Speichern sind im Hook versteckt (eigene Darstellung).
Eigene Hooks sind Reacts Antwort auf die Frage der Wiederverwendung von Logik, so wie Komponenten Oberfläche wiederverwenden. Ein guter eigener Hook kennt die Anwendung nicht: useLocalStorage funktioniert für einen Kommentarentwurf genauso wie für einen Bestwert im Quiz.
Auflösung
(a) useRef für das Feld und inputRef.current?.focus() in einem useEffect. (b) useEffect mit [] und Cleanup, das Ergebnis landet über useState in der Anzeige. (c) useContext, ein querschnittlicher, selten wechselnder Wert. (d) Gar kein Hook: Die Zahl wird beim Rendern aus der Liste der markierten Aufträge berechnet.
Stolperfallen
Abschnitt betitelt „Stolperfallen“- Effekt ohne Dependency-Array, mit Setter darin: Endlosschleife aus Rendern und Effekt.
useEffectfür abgeleitete Werte: eine gefilterte Liste in einem Effekt in einen zweiten State schreiben, statt sie beim Rendern zu berechnen.- Cleanup vergessen: Timer laufen weiter, überholte Antworten überschreiben neuere.
- Ref für Sichtbares: Eine Änderung an
ref.currenterscheint nicht auf der Seite, weil sie kein Rendern auslöst. - Alles in den Context: Jeder Zustand wird global, der Datenfluss ist nicht mehr nachvollziehbar.
- Vorsorglich memoisiert:
useMemoum jede Kleinigkeit macht den Code komplizierter, ohne messbaren Gewinn.
Zusatzmaterialien
Abschnitt betitelt „Zusatzmaterialien“- react.dev: Synchronizing with effects - Effekte, Abhängigkeiten und Cleanup Schritt für Schritt.
- react.dev: You might not need an effect - wann ein Effekt der falsche Weg ist; die wichtigste Lektüre gegen die häufigsten Effekt-Fehler.
- react.dev: Passing data deeply with context - Context gegen Prop Drilling.
- react.dev: Reusing logic with custom hooks - eigene Hooks entwerfen und benennen.
Zum Anhören
Abschnitt betitelt „Zum Anhören“Du kannst dir diese Einheit kostenlos mit der Vorlesefunktion deines Browsers oder Betriebssystems vorlesen lassen (etwa „Laut vorlesen” in Edge), ohne Konto und ohne Upload; die Codebeispiele liest du dabei am Bildschirm mit. Freiwillig kannst du mit Google NotebookLM (Google-Konto nötig) aus diesem öffentlichen Text eine gesprochene Zusammenfassung erzeugen, auch auf Deutsch.
Lernergebnisse
Abschnitt betitelt „Lernergebnisse“Du kannst jetzt:
- Seiteneffekte mit
useEffectumsetzen, mit Dependency-Array und Cleanup, und die Endlosschleifen-Falle vermeiden. useReffür DOM-Zugriff und unsichtbare Merkwerte einsetzen und von State abgrenzen.- die Einsatzgebiete von
useContext,useMemounduseCallbacknennen und in fremdem Code erkennen. - wiederverwendbare Zustandslogik in einen eigenen Hook auslagern.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe 07-6a - Quiz reloaded, alle Stufen
Quellen
Abschnitt betitelt „Quellen“useEffect, Dependency-Array, Cleanup, doppelter Lauf inStrictMode→ react.dev: useEffect, Synchronizing with effects- Abgeleitete Werte statt Effekt → react.dev: You might not need an effect
useRef→ react.dev: useRef- Context, Schreibweise ab React 19 und
.Providerdavor → react.dev: createContext, Passing data deeply with context useMemo,useCallback→ react.dev: useMemo, useCallback- Eigene Hooks, Lazy-Form von
useState→ react.dev: Reusing logic with custom hooks, useState localStoragespeichert Strings → MDN: Window: localStorage property- Abb. 7.23 (Hook-Familie), Abb. 7.24 (Effekt und Cleanup), Abb. 7.25 (Prop Drilling gegen Context), Abb. 7.26 (eigener Hook) → eigene Darstellung, alle als Mermaid-Diagramm
Autoren
Abschnitt betitelt „Autoren“- Oliver Maurhart (MAO)
- Michael Gabriel (GAM)
CC-BY-SA