Ein Jahr React Hooks-API – eine Bilanz
Seite 4: Werte über Renderzyklen hinweg erhalten
Eine Herausforderung beim Verwenden von Funktionskomponenten haben Entwickler, wenn die Komponente einen Wert speichern soll, der über mehrere Renderzyklen hinweg vorgehalten, aber nicht im State liegen soll. In der Klassen-API wurde dazu einfach eine Instanzvariable an der Klasse hinzugefügt.
Als Beispiel dient eine Timer-Komponente. Sie startet einen Timer, den Anwender durch das Klicken auf einen Button beenden können. Das bedeutet, dass die id
des Timers außerhalb der Effektfunktion bekannt sein muss. Die Klassen-API legt die id
als Instanzvariable der Komponentenklasse ab (this.id = ...
). In der Funktionskomponente nutzt man dazu den Hook useRef
, der Werte aufnimmt, die über mehrere Renderzyklen hinweg erhalten bleiben sollen:
export default function App() {
const [running, setRunning] = useState(false);
// Eine Referenz ist ein "Datenhalter", der �ber
// Renderzyklen hinweg erhalten bleibt
const timerRef = useRef();
// In cancel wird die id des zu beendenden Timers ben�tigt
function cancel() { clearTimeout(timerRef.current); }
useEffect(() => {
const id = setTimeout(() => setRunning(false), 2000);
// Objekte, die mit useRef erzeugt werden, haben ein
// Property current, das beliebige Werte aufnehmen kann,
// die auch in folgenden Renderzyklen zur Verf�gung
// stehen
timerRef.current = id;
setRunning(true);
return () => clearTimeout(id);
}, []);
return <button onClick={cancel}>
{running ? "Timer is running" : "Timer finished"}
</button>;
}
Etwas zugespitzt lässt sich sagen, das useRef
Probleme löst, die die Klassen-API mit Standard-JavaScript-Mitteln (Instanzvariablen) in den Griff kriegt. Entwickler müssen also neue APIs lernen und anwenden. Ähnliches gilt für useCallback
und useMemo
, die dazu dienen, Werte beziehungsweise Callback-Funktionen über Renderzyklen hinweg zu cachen und so stabil zu halten.
Eigene Hooks
Entwickler können eigene Hooks (Custom Hooks) schreiben, durch die es einfacher sein soll, wiederverwendbaren (Infrastruktur-)Code zu schreiben.
Im Beispiel zum useEffect
-Hook ist zu sehen, wie Anwender Daten über eine API laden können. Das ist eine häufig wiederkehrende Anforderung in React-Anwendungen und meistens sieht der Code dazu ähnlich aus. Es bietet sich an, den Code wiederverwenden zu können. Das ist mit dem Klassenkomponenten allerdings nicht oder nur schwer möglich, unter anderem durch die Verteilung der Logik auf mehrere Lifecycle-Methoden und die Abhängigkeiten von this.state
und this.props
.
Mit der Hooks-API können Entwickler einen eigenen Hook bauen, der die Logik zum Laden der Daten enthält. Hook-Funktionen sind reguläre JavaScript-Funktionen, deren Namen mit "use" beginnen müssen. Die Funktionssignatur und der Rückgabewert von Hook-Funktionen kann man selbst bestimmen. Außerdem ist es möglich, in Hooks andere Hooks zu verwenden.
Im folgenden Beispiel erwartet der useFetch
-Hook eine URL, von der er die Daten laden soll. Die geladenen Daten landen im State des Hooks, was dazu führt, dass die Komponente, die den Hook verwendet, sich neu rendert und die Daten darstellen kann. Wenn die übergebene URL sich ändert, führt das zu einem Neuladen der Daten, da die URL als Abhängigkeit vom useEffect
-Hook eingetragen ist. Entwickler können den useFetch
-Hook nun in diversen Komponenten einsetzen.
function useFetch(url) {
const [data, setData] = React.useState(null);
React.useEffect( () => {
fetch(url)
.then(response => response.json())
.then(json => setData(json));
}, [url]);
return data;
}
function BlogPost({postId}) {
const blogPost = useFetch(`/api/blog/${postId}`);
if (!blogPost) {
return <h1>Bitte Geduld, Blog-Post wird geladen...</h1>
}
return ...;// Geladenen Blog-Post rendern
}
function UserSettings({userId}) {
const userSettings = useFetch(`/api/user/${userId}`);
...
}
Es gibt mittlerweile eine ganze Reihe von Hooks, die die Community erstellt und veröffentlicht hat. Eine Übersicht gibt es unter anderem bei usehooks.com.