Extrahiere zustandsbehaftete Logik in wiederverwendbare, komponierbare und testbare Hooks.
Diese Lektion in Kodokon öffnenEin eigener Hook ist eine Funktion, deren Name mit use beginnt und die andere Hooks aufruft. Er extrahiert zustandsbehaftete Logik, nicht geteilten Zustand: Jede Komponente, die ihn aufruft, erhält ihr eigenes useState und useEffect, vollständig isoliert. Er ist Reacts direktestes Werkzeug, um Logik auszulagern - wo HOCs und Render-Props dem Baum Schichten hinzufügten, schlüpft ein Hook in den Körper der Komponente, lässt sich leicht komponieren und ist einfach zu testen.
import { useEffect, useState } from "react";
function useDebouncedValue(value, delay = 300) {
const [debounced, setDebounced] = useState(value);
useEffect(() => {
const id = setTimeout(() => {
setDebounced(value);
}, delay);
return () => clearTimeout(id);
}, [value, delay]);
return debounced;
}Das use-Präfix ist nicht dekorativ: Es ist das, was dem ESLint-Plugin react-hooks erlaubt, die Regeln der Hooks innerhalb deiner Funktion zu prüfen - bedingungslose Aufrufe, auf oberster Ebene. Achte auch auf die Form der zurückgegebenen API: ein [value, setValue]-Tupel, wenn die Nutzung useState spiegelt, ein Objekt mit benannten Schlüsseln, sobald es mehr als zwei Dinge offenzulegen gibt.
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const stored = localStorage.getItem(key);
return stored !== null
? JSON.parse(stored)
: initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}Hooks komponieren: Ein Hook kann andere Hooks aufrufen, auch deine eigenen. So entstehen lesbare fachliche Abstraktionen, aufgebaut auf generischen Bausteinen. Beachte die Kombination von AbortController mit der Aufräumfunktion: Ohne sie kann eine Anfrage, die für eine ältere Eingabe abgeschickt wurde, nach der neuesten antworten und deren Ergebnis überschreiben - die klassische Race Condition von Suchfeldern.
function useUserSearch(query) {
const debounced = useDebouncedValue(query, 400);
const [results, setResults] = useState([]);
useEffect(() => {
if (debounced === "") {
setResults([]);
return;
}
const controller = new AbortController();
fetch("/api/search?q=" + debounced, {
signal: controller.signal,
})
.then((res) => res.json())
.then(setResults)
.catch(() => {});
return () => controller.abort();
}, [debounced]);
return results;
}