Praktische Muster für benutzerdefinierte React-Hooks
Lukas Schneider
DevOps Engineer · Leapcell

Einleitung
In der sich rasant entwickelnden Landschaft der modernen Webentwicklung hat sich React als führende Bibliothek für die Erstellung reichhaltiger, interaktiver Benutzeroberflächen etabliert. Ein wesentlicher Beitrag zur Leistungsfähigkeit und Flexibilität von React ist die Einführung von Hooks, die es uns ermöglichen, Zustandslogik und Seiteneffekte in funktionale Komponenten zu integrieren. Während React eine Reihe integrierter Hooks wie useState und useEffect bereitstellt, liegt die wahre Magie oft in der Erstellung benutzerdefinierter Hooks. Diese benutzerdefinierten Hooks ermöglichen es uns, komplexe Komponentenlogik zu extrahieren und wiederzuverwenden, was zu saubereren, besser wartbaren und hochgradig zusammensetzbaren Codebasen führt. Dieser Artikel befasst sich mit gängigen, äußerst praktischen Mustern zum Erstellen benutzerdefinierter React-Hooks und veranschaulicht diese Konzepte anhand zweier weit verbreiteter Beispiele: useDebounce und useLocalStorage.
Die Kernkonzepte von benutzerdefinierten Hooks verstehen
Bevor wir uns mit spezifischen Beispielen befassen, wollen wir ein gemeinsames Verständnis der grundlegenden Prinzipien hinter benutzerdefinierten Hooks schaffen.
Was sind benutzerdefinierte Hooks?
Benutzerdefinierte Hooks sind JavaScript-Funktionen, deren Namen mit use beginnen und die andere Hooks aufrufen können. Sie kapseln Logik, die über verschiedene Komponenten hinweg wiederverwendet werden kann, ohne Code zu duplizieren. Das Präfix „use“ ist eine Konvention, die es dem React-Linter ermöglicht, die Regeln von Hooks durchzusetzen und sicherzustellen, dass Hooks nur auf der obersten Ebene von benutzerdefinierten Hooks oder funktionalen Komponenten aufgerufen werden.
Vorteile von benutzerdefinierten Hooks:
- Wiederverwendbarkeit der Logik: Extrahieren und Teilen von nicht-visueller Logik.
- Verbesserte Lesbarkeit: Komponenten sauberer und auf die Darstellung fokussiert machen.
- Erweiterte Wartbarkeit: Zentralisierte Logik erleichtert die Verwaltung von Änderungen.
- Bessere Testbarkeit: Isolierte Logik lässt sich leichter unabhängig testen.
Regeln von Hooks:
- Hooks nur auf oberster Ebene aufrufen: Rufen Sie Hooks nicht innerhalb von Schleifen, Bedingungen oder verschachtelten Funktionen auf.
- Hooks nur aus React-Funktionen aufrufen: Rufen Sie Hooks aus funktionalen React-Komponenten oder anderen benutzerdefinierten Hooks auf.
Mit diesen grundlegenden Konzepten können wir nun untersuchen, wie zwei äußerst nützliche benutzerdefinierte Hooks erstellt werden.
Benutzerdefinierter Hook-Muster 1: Verzögern von Zustandsaktualisierungen mit useDebounce
Eine häufige Herausforderung beim Erstellen interaktiver Benutzeroberflächen ist die Handhabung von häufigen Benutzereingaben, z. B. beim Tippen in eine Suchleiste oder beim Ändern der Fenstergröße. Das Auslösen eines Ereignisbehandlers oder API-Aufrufs für jeden einzelnen Tastendruck kann zu Leistungsproblemen oder unnötiger Serverlast führen. Die „Debouncing“-Technik löst dies, indem die Ausführung einer Funktion verzögert wird, bis eine bestimmte Zeitspanne ohne weitere Auslöser vergangen ist.
Das Problem, das useDebounce löst
Stellen Sie sich eine Such-Eingabe vor. Während der Benutzer tippt, möchten Sie möglicherweise Suchergebnisse abrufen. Wenn Sie bei jedem onChange-Ereignis einen API-Aufruf auslösen und der Benutzer schnell tippt, führen Sie viele unnötige Anfragen aus. Debouncing stellt sicher, dass die Suchanfrage erst gesendet wird, nachdem der Benutzer eine Pause von einer bestimmten Dauer beim Tippen eingelegt hat.
Implementierung von useDebounce
Der useDebounce-Hook nimmt typischerweise einen Wert und eine Verzögerung als Eingabe und gibt eine entprellte Version dieses Werts zurück.
import { useState, useEffect } from 'react'; /** * Benutzerdefinierter Hook zum Entprellen eines Wertes. * * @param {any} value Der zu entprellende Wert. * @param {number} delay Die Verzögerung in Millisekunden, bevor der entprellte Wert aktualisiert wird. * @returns {any} Der entprellte Wert. */ function useDebounce(value, delay) { // Zustand zum Speichern des entprellten Werts const [debouncedValue, setDebouncedValue] = useState(value); useEffect(() => { // Setze einen Timeout, um den entprellten Wert nach der angegebenen Verzögerung zu aktualisieren const handler = setTimeout(() => { setDebouncedValue(value); }, delay); // Räume den Timeout auf, wenn sich der Wert oder die Verzögerung ändert, bevor er ausgelöst wird // oder wenn die Komponente unmountet wird. return () => { clearTimeout(handler); }; }, [value, delay]); // Nur erneut ausführen, wenn sich Wert oder Verzögerung ändern return debouncedValue; } export default useDebounce;
Wie useDebounce funktioniert
useStatefür den entprellten Wert:debouncedValuespeichert die stabile, entprellte Version vonvalue. Sie wird mit dem anfänglichenvalueinitialisiert.useEffectfür die Entprall-Logik:- Innerhalb von
useEffectwird einsetTimeoutgesetzt, umdebouncedValuenachdelayMillisekunden zu aktualisieren. - Bereinigungsfunktion:
useEffectgibt eine Bereinigungsfunktion zurück, dieclearTimeoutaufruft. Dies ist entscheidend. Jedes Mal, wennvalueoderdelaysich ändern, wird der vorherige Timeout gelöscht und ein neuer gesetzt. Dies stellt sicher, dasssetDebouncedValuenur dann ausgelöst wird, wennvaluefür die Dauer vondelayunverändert geblieben ist.
- Innerhalb von
- Abhängigkeitsarray:
[value, delay]stellt sicher, dass der Effekt nur neu ausgeführt wird, wenn sichvalueoderdelayändern.
Anwendung von useDebounce
Sehen wir uns an, wie dieser Hook in einer Suchkomponente verwendet werden kann:
import React, { useState } from 'react'; import useDebounce from './useDebounce'; // Angenommen, Sie haben den Hook in einer Datei gespeichert function SearchInput() { const [searchTerm, setSearchTerm] = useState(''); // Verwende den entprellten Suchbegriff const debouncedSearchTerm = useDebounce(searchTerm, 500); // 500ms Entprall-Verzögerung // Effekt zur Ausführung der Suche, wenn sich der debouncedSearchTerm ändert useEffect(() => { if (debouncedSearchTerm) { console.log('Suche wird durchgeführt für:', debouncedSearchTerm); // In einer realen Anwendung würden Sie hier einen API-Aufruf tätigen // z. B. fetch(`/api/search?q=${debouncedSearchTerm}`).then(...) } else { console.log('Suchbegriff gelöscht.'); } }, [debouncedSearchTerm]); // Läuft nur, wenn sich debouncedSearchTerm ändert const handleChange = (event) => { setSearchTerm(event.target.value); }; return ( <div> <input type="text" placeholder="Zum Suchen tippen..." value={searchTerm} onChange={handleChange} style={{ padding: '8px', width: '300px' }} /> <p>Aktueller Suchbegriff: {searchTerm}</p> <p>Entprellter Suchbegriff: {debouncedSearchTerm}</p> </div> ); } export default SearchInput;
In diesem Beispiel aktualisiert SearchInput seinen internen searchTerm sofort, aber der useEffect, der die hypothetische Suchoperation auslöst, reagiert nur auf debouncedSearchTerm und sorgt so für Effizienz.
Benutzerdefinierter Hook-Muster 2: Zustand speichern mit useLocalStorage
Viele Webanwendungen erfordern die Persistierung von Benutzereinstellungen oder Daten zwischen Seitenneuladungen. localStorage ist eine praktische Browser-API dafür. Die direkte Interaktion mit localStorage innerhalb von Komponenten kann jedoch zu wiederholtem Code und potenziellen Problemen mit der anfänglichen Zustandsinitialisierung führen. Ein useLocalStorage-Hook vereinfacht diesen Prozess.
Das Problem, das useLocalStorage löst
Das Speichern und Abrufen von Werten aus localStorage erfordert oft Boilerplate-Code: JSON.parse und JSON.stringify, Fehlerbehandlung und die Sicherstellung, dass der Anfangszustand der Komponente den gespeicherten Wert korrekt widerspiegelt. useLocalStorage abstrahiert diese Komplexität und macht die Verwaltung persistenter Zustände einfach.
Implementierung von useLocalStorage
Dieser Hook akzeptiert einen Schlüssel für localStorage und einen Anfangswert. Er gibt den aktuellen Wert und eine Funktion zum Aktualisieren zurück, ähnlich wie useState.
import { useState, useEffect } from 'react'; /** * Benutzerdefinierter Hook zum Speichern von Zustand in localStorage. * * @param {string} key Der Schlüssel, unter dem der Wert in localStorage gespeichert wird. * @param {any} initialValue Der Anfangswert, der verwendet wird, wenn nichts in localStorage gefunden wird. * @returns {[any, (value: any) => void]} Ein Tupel, das den aktuellen Zustand und eine Setter-Funktion enthält. */ function useLocalStorage(key, initialValue) { // Verwende eine funktionale Aktualisierung von `useState` für die verzögerte Initialisierung. // Dies stellt sicher, dass `localStorage.getItem` nur einmal aufgerufen wird. const [storedValue, setStoredValue] = useState(() => { try { const item = window.localStorage.getItem(key); // Parsen des gespeicherten JSON oder wenn keines vorhanden ist, initialValue zurückgeben return item ? JSON.parse(item) : initialValue; } catch (error) { // Wenn ein Fehler auftritt (z. B. localStorage nicht verfügbar), // initialValue zurückgeben und den Fehler protokollieren. console.error('Fehler beim Lesen von localStorage:', error); return initialValue; } }); // Verwende useEffect, um localStorage zu aktualisieren, sobald sich der storedValue ändert useEffect(() => { try { window.localStorage.setItem(key, JSON.stringify(storedValue)); } catch (error) { console.error('Fehler beim Schreiben in localStorage:', error); } }, [key, storedValue]); // Effekt erneut ausführen, wenn sich key oder storedValue ändert return [storedValue, setStoredValue]; } export default useLocalStorage;
Wie useLocalStorage funktioniert
- Verzögerte Initialisierung mit
useState:- Der
useState-Hook wird mit einer Funktion initialisiert. Diese Funktion wird nur einmal während des anfänglichen Renderings der Komponente ausgeführt. - Innerhalb dieser Funktion wird versucht, das Element über den bereitgestellten
keyauslocalStorageabzurufen. - Es parst den abgerufenen String mithilfe von
JSON.parse. Wenn kein Element gefunden wird oder ein Fehler auftritt, greift er auf deninitialValuezurück. Dies verhindert den Zugriff auflocalStoragebei jedem Rendering.
- Der
useEffectfür die Persistenz:- Dieser
useEffect-Hook wird ausgeführt, wenn sichkeyoderstoredValueändern. - Er nimmt den aktuellen
storedValue, wandelt ihn mitJSON.stringifyin einen JSON-String um und speichert ihn unter dem angegebenenkeyinlocalStorage. - Eine Fehlerbehandlung ist für eine robuste Funktionalität enthalten.
- Dieser
- Rückgabewert: Der Hook gibt
[storedValue, setStoredValue]zurück und spiegelt die API vonuseStatewider.
Anwendung von useLocalStorage
Betrachten Sie eine Komponente, die es einem Benutzer ermöglicht, eine Dunkelmodus-Einstellung umzuschalten:
import React from 'react'; import useLocalStorage from './useLocalStorage'; // Angenommen, Sie haben den Hook gespeichert function ThemeSwitcher() { // Verwende unseren benutzerdefinierten Hook, um den 'isDarkMode'-Zustand zu verwalten, // standardmäßig auf false, wenn er nicht in localStorage gefunden wird. const [isDarkMode, setIsDarkMode] = useLocalStorage('isDarkMode', false); const toggleDarkMode = () => { setIsDarkMode(!isDarkMode); // Sie würden normalerweise eine CSS-Klasse zum body- oder Root-Element hinzufügen // document.body.classList.toggle('dark-mode', !isDarkMode); }; useEffect(() => { // Wende Stile basierend auf dem aktuellen Dunkelmodus-Zustand an document.body.style.backgroundColor = isDarkMode ? '#333' : '#FFF'; document.body.style.color = isDarkMode ? '#FFF' : '#333'; }, [isDarkMode]); return ( <div style={{ padding: '20px', border: '1px solid #ccc', margin: '20px' }}> <h2>Themenwechsler</h2> <p>Aktuelles Thema: {isDarkMode ? 'Dunkel' : 'Hell'}</p> <button onClick={toggleDarkMode}> Umschalten auf {isDarkMode ? 'Hell' : 'Dunkel'} Modus </button> <p>Aktualisieren Sie die Seite, um zu sehen, dass der Zustand erhalten bleibt!</p> </div> ); } export default ThemeSwitcher;
Mit useLocalStorage wird die Verwaltung des isDarkMode-Zustands so einfach wie die Verwendung von useState, aber sein Wert bleibt automatisch über Browsersitzungen hinweg erhalten.
Fazit
Benutzerdefinierte React-Hooks bieten einen eleganten und leistungsstarken Mechanismus zum Abstrahieren, Wiederverwenden und Organisieren von Zustandslogik in Ihrer Anwendung. Durch das Verständnis gängiger Muster wie Debouncing von Kontrollflüssen und Persistierung von Zuständen können Entwickler effizientere, robustere und wartbarere React-Anwendungen erstellen. Die Beispiele useDebounce und useLocalStorage demonstrieren praktische Anwendungen dieser Muster und vereinfachen gängige Entwicklungsherausforderungen erheblich. Die Einführung benutzerdefinierter Hooks führt zu saubereren Komponenten, verbesserter Wiederverwendbarkeit und einem angenehmeren Entwicklererlebnis.

