Ein Custom Hook ist eine Funktion, deren Name mit use beginnt und die andere Hooks aufruft. Du schreibst einen, wenn zwei Komponenten dieselbe zustandsbehaftete Logik brauchen: Verschiebe die Aufrufe von useState und useEffect in die Funktion, gib zurück, was die Komponente braucht, und ruf sie aus jeder Komponente auf.
Beide Spoiler rufen useToggle auf, und einen zu öffnen lässt den anderen geschlossen. Jeder Aufruf führt sein eigenes useState aus, also bekommt jede Komponente ihren eigenen Wert. Ein Custom Hook teilt Logik, keinen State.
Die Regeln zum Schreiben
- Der Name beginnt mit
useund einem Großbuchstaben:useToggle,useOnlineStatus. Die Lint-Regel für Hooks findet Hook-Aufrufe über das Präfix und prüft sie. - Er ruft mindestens einen Hook auf. Ein Helfer, der keine Hooks aufruft, ist eine gewöhnliche Funktion; nenn ihn
formatDate, nichtuseFormatDate, damit er überall aufgerufen werden kann, auch in Bedingungen. - Er folgt den Regeln der Hooks: Hooks auf seiner obersten Ebene aufrufen, und ihn nur aus Komponenten oder anderen Custom Hooks aufrufen.
- Er gibt zurück, was nützlich ist. Ein Array (
[on, toggle]) ist praktisch, wenn Aufrufer die Werte umbenennen; ein Objekt ({ data, loading, error }) liest sich ab drei Werten besser.
useLocalStorage: State, der ein Neuladen übersteht
useState vergisst beim Neuladen alles. Dieser Hook liest den Startwert aus localStorage und schreibt jede Änderung zurück.
Die Vorschau läuft in einem Sandbox-Frame, in dem der Browser localStorage blockiert, also siehst du hier die Warnung in der Konsole, und der Hook fällt auf einfachen State zurück. Auf einer normalen Seite übersteht der Name ein Neuladen. Das try/catch ist nicht nur für diese Vorschau da: Private Modi, blockierte Cookies und ein volles Kontingent lassen den Speicher auch in echten Apps Fehler werfen. Eine Funktion an useState zu übergeben heißt, dass der Speicher einmal gelesen wird, beim ersten Render.
useDebounce: warten, bis nicht mehr getippt wird
Ein Suchfeld sollte nicht bei jedem Tastendruck eine Anfrage auslösen. useDebounce gibt eine Kopie eines Werts zurück, die sich erst aktualisiert, wenn er sich für eine bestimmte Verzögerung nicht mehr geändert hat.
Tippe schnell ein Wort: Typed folgt jeder Taste, Debounced zieht eine halbe Sekunde nach, wenn du aufhörst, und die Konsole loggt eine Suche. Jeder Tastendruck ändert value, also führt React das Cleanup (clearTimeout) aus, bevor es einen neuen Timer startet. Ändere 500 zu 0, und jeder Tastendruck loggt seine eigene Suche.
useWindowWidth: abonnieren mit Cleanup
Ein Hook, der auf den Browser hört, muss seinen Listener entfernen, wenn die Komponente verschwindet, sonst hinterlässt jedes Mounten einen.
Ändere die Größe des Browserfensters, und die Zahl folgt. Klicke auf den Button, und die Konsole zeigt, wie der Listener entfernt wird. Lösch das return aus dem Effekt und mounte und unmounte ein paar Mal: Du bekommst jedes Mal eine Zeile added und keine removed, also häufen sich die alten Listener an. Für Browserwerte wie diesen ist useSyncExternalStore der Hook, den React genau dafür gebaut hat, und er lässt sich gut in einem Custom Hook aufrufen.
Logik wird geteilt, State nicht
Zwei Komponenten, die denselben Hook aufrufen, bekommen zwei getrennte Kopien von allem darin. Das überrascht Leute, die erwarten, dass ein Hook wie eine globale Variable funktioniert.
Klicke auf einen der Buttons, und nur dieser Zähler bewegt sich. Wenn beide dieselbe Zahl zeigen sollen, verschiebe den State in App und reich ihn nach unten oder leg ihn in Context. Ein Custom Hook kann diesen geteilten State bequem verpacken (useCart(), das einen CartContext liest), aber das Teilen kommt vom Context, nicht vom Hook.
Wann du einen Custom Hook schreibst
Lagere einen Hook aus, wenn dieselbe Gruppe von Hook-Aufrufen in zwei Komponenten auftaucht oder wenn die Effektlogik einer Komponente so lang ist, dass ein Name sie besser erklären würde (useOnlineStatus() liest sich besser als zwanzig Zeilen Listener). Pack keinen einzelnen useState-Aufruf in einen Hook, nur um ihn umzubenennen, und schreib keine Hooks, die nur useEffect aufrufen, um State aus Props zu setzen; berechne diesen Wert stattdessen beim Rendern.
Häufig gestellte Fragen
Was ist ein Custom Hook in React?
Eine JavaScript-Funktion, deren Name mit use beginnt und die einen oder mehrere andere Hooks aufruft. Sie verpackt zustandsbehaftete Logik (State, Effekte, Refs), damit jede Komponente sie durch Aufruf der Funktion wiederverwenden kann.
Teilen Custom Hooks State zwischen Komponenten?
Nein. Jeder Aufruf eines Custom Hooks führt seine eigenen Aufrufe von useState und useEffect aus, also bekommt jede Komponente unabhängigen State. Um denselben Wert zwischen Komponenten zu teilen, verlagere den State nach oben oder leg ihn in Context oder einen externen Store.
Warum muss der Name eines Custom Hooks mit use beginnen?
Der Name sagt den Lint-Regeln von React und anderen Entwicklern, dass die Funktion Hooks aufruft, also gelten für sie die Regeln der Hooks: auf oberster Ebene aufrufen, nie in einer Bedingung oder Schleife. Ohne das Präfix kann der Linter die Aufrufe darin nicht prüfen.
Kann ein Custom Hook JSX zurückgeben?
Er kann alles zurückgeben, aber wenn er JSX zurückgibt, ist er meist besser als Komponente geschrieben. Custom Hooks geben typischerweise Werte und Funktionen zurück, als Array ([value, toggle]) oder als Objekt ({ data, error, loading }).
Kann ich einen Custom Hook in useEffect oder einem Event-Handler aufrufen?
Nein. Ein Custom Hook ist weiterhin ein Hook, also folgt er den Regeln der Hooks: Ruf ihn auf der obersten Ebene einer Komponente oder eines anderen Custom Hooks auf.