10 Unterschätzte React Hooks, die Sie wahrscheinlich noch nicht verwenden
Seit der Veröffentlichung von React 16.8 sind Hooks das Fundament der modernen React Frontend Entwicklung und fördern die Verwendung der funktionalen Komponenten anstelle von Klassen. Dennoch verwenden die meisten Entwickler kaum mehr als die beiden bekannten Hooks useState und useEffect.
Diese Hooks sind zwar für allgemeine Zwecke praktisch, doch verbirgt das React-Ökosystem eine Reihe weniger bekannter Hooks, die die Leistung, Lesbarkeit und Skalierbarkeit erheblich verbessern können.
In diesem React-Hooks-Tutorial stellen wir Ihnen 10 React-Hooks vor, die Sie vermutlich noch nicht kennen. Wir erklären, wann Sie sie einsetzen sollten, führen kurze Codebeispiele an und zeigen, wie React 19 die Funktionalität der Hooks weiter ausbaut. Egal, ob Sie gerade eine neue App von Grund auf entwickeln oder Ihre React-Leistung mit weniger bekannten Hooks verbessern möchten, dieser Artikel bietet Ihnen einen klaren Überblick über den tatsächlichen Nutzen dieser Hooks.
Warum Hooks?
React-Hooks sollen den Umgang mit State und Nebeneffekten in funktionalen Komponenten vereinfachen. Statt sich mit Lifecycle-Methoden oder HOCs zu beschäftigen, ermöglichen Hooks Entwicklern, Logik wiederzuverwenden und Komponenten klein sowie aussagekräftig zu halten.
Während sich React weiterentwickelt, festigt sich diese Philosophie immer mehr. Das React-Team arbeitet ständig daran, das gleichzeitige Rendern zu verbessern, bessere Übergänge zu gewährleisten und fortschrittliche React-Hooks zu erforschen, die in zukünftigen Versionen vorgestellt werden sollen.
Lassen Sie uns nun einige unterschätzte React-Hooks genauer ansehen, mit denen Sie Ihre React-Anwendung optimal nutzen können.
1. useLayoutEffect: Der synchronisierte Nebeneffekt
Manche Entwickler meiden useLayoutEffect, weil sie befürchten, dass dieser Hook die Benutzeroberfläche blockieren könnte (gleich sehen wir warum). Wenn Sie useLayoutEffect richtig einsetzen, kann Ihnen dieser Hook viele versteckte Layout-Fehler ersparen.
Vergleichen wir nun useLayoutEffect mit dem bekannten useEffect. Wahrscheinlich wissen Sie schon, dass man useEffect verwendet, um Nebeneffekte auszuführen, die nach dem Rendering durch den Browser ablaufen. Im Gegensatz dazu wird useLayoutEffect synchron zu allen DOM-Änderungen vor dem Rendering ausgelöst, was diesen Hook perfekt zum Messen oder Verändern von Layout-Werten macht.
Werfen wir einen Blick auf den folgenden Code:
import { useLayoutEffect, useRef, useState } from "react";
function Tooltip() {
const ref = useRef(null);
const [width, setWidth] = useState(0);
useLayoutEffect(() => {
if (ref.current) setWidth(ref.current.offsetWidth);
}, []);
return <div ref={ref}>Tooltip width: {width}px</div>;
}
In diesem Beispiel wird die Breite des Tooltips direkt angewendet, bevor dessen Inhalt auf dem Bildschirm „gepaintet“ wird (also unmittelbar bevor er Ihnen als gerendertes Element angezeigt wird). Dadurch lassen sich sogenannte „Spring“-Effekte vermeiden, die häufig bei der Verwendung von useEffect auftreten.
Der mögliche Nachteil ist: da der Effekt vor dem Paint ausgelöst wird, wird die Benutzeroberfläche blockiert, bis der Code innerhalb von useLayoutEffect vollständig ausgeführt ist. Die Faustregel lautet daher, diesen Hook mit Bedacht einzusetzen, hauptsächlich für Layout-Berechnungen, Animationen oder Synchronisationsaufgaben, die tatsächlich vor dem Bildschirm-Update erfolgen müssen
2. useRef: Der geheime Wächter
In der Regel verwenden Entwickler diesen Hook, um auf DOM-Knoten zuzugreifen, indem sie das ref an ein bestimmtes JSX-Element, wie zum Beispiel ein Eingabefeld, übergeben. Später kann eine solche ref genutzt werden, um imperativ mit dem Knoten zu interagieren.
Abgesehen von diesem bekannten Anwendungsfall liegt die wahre Stärke von useRef darin, dass man veränderliche Werte speichern kann, die über Render-Zyklen hinweg erhalten bleiben, ohne einen erneuten Render-Auslöser zu verursachen. Deswegen gehört useRef zu den unterschätzten Hooks.
So funktioniert es:
import { useRef, useEffect } from "react";
function Timer() {
const count = useRef(0);
useEffect(() => {
const interval = setInterval(() => {
count.current += 1;
console.log("Seconds elapsed:", count.current);
}, 1000);
return () => clearInterval(interval);
}, []);
return <p>Open your console to see the timer running!</p>;
}
Hier kann count als eine Art versteckte Variable betrachtet werden, die von React nicht überwacht wird. Schließlich kann der Wert innerhalb einer solchen Variable aktualisiert werden: Die Benutzeroberfläche muss dabei nicht neu gerendert werden, da nur der neue Wert als Referenz gespeichert wird. Daher passt useRef hervorragend, um vorherige Werte zu speichern, Timeouts zu verwalten und stabile Referenzen zu erstellen, ohne Renderings auszulösen.
3. useImperativeHandle: Steuerung der Sichtbarkeit
Wie bereits erwähnt, können wir useRef verwenden, um direkt mit dem DOM zu interagieren. In manchen Fällen möchten Sie jedoch die interne Struktur einer Komponente verbergen und nur einen kleinen Teil der Interaktion für den Elternteil zugänglich machen. Dafür ist useImperativeHandle eine gute Wahl.
Dieser Hook ermöglicht es,
- i) den Wert zu steuern, der von einer bestimmten Referenz zurückgegeben wird. Beispielsweise können Sie anstelle der Rückgabe des Instanzelements explizit angeben, welcher Wert für diese Referenz zurückgegeben werden soll:
import { useImperativeHandle, useRef } from "react";
const Modal = ({ ref }) => {
const dialogRef = useRef();
useImperativeHandle(ref, () => ({
open: () => dialogRef.current.showModal(),
close: () => dialogRef.current.close(),
}));
return <dialog ref={dialogRef}>Hello World</dialog>;
};
In diesem Beispiel wird die <Modal>-Komponente (über ihre ref) die Methoden zum Öffnen und Schließen bereitstellen, anstatt die gesamte Instanz des Dialogelements.
- ii) Native Funktionen (wie blur, focus usw.) können mit eigenen Funktionen überschrieben werden, wodurch Nebeneffekte zum normalen Verhalten hinzugefügt werden oder das Verhalten vollständig verändern.
import { useState, useRef, useImperativeHandle } from 'react';
const MyInput = ({ ref, ...props }) => {
const [val, setVal] = useState("");
const inputRef = useRef();
useImperativeHandle(ref, () => ({
blur: () => {
// Additional side effect
document.title = val;
// Blur the input element
inputRef.current.blur();
}
}));
return (
<input
ref={inputRef}
val={val}
onChange={e => setVal(e.target.value)}
{...props}
/>
);
}
In diesem Beispiel greift die übergeordnete Komponente auf eine benutzerdefinierte blur-Methode zu, die einen Nebeneffekt mit dem Standardverhalten des Eingabefelds kombiniert.
4. useReducer vs useState: Intelligentes Zustandsmanagement
Ein grundlegender Ansatz bei der Verwendung von State-Management-Hooks besteht darin, für jeden spezifischen Zustand, den Sie verwalten möchten, eine separate useState-Instanz hinzuzufügen. Wenn Ihre Anwendung sich jedoch skaliert, wird der Zustand komplexer. Hier kommt useReducer ins Spiel.
useReducer basiert auf dem Redux-Muster, bei dem das Zustandsmanagement um das Versenden von Aktionen und deren Verarbeitung in einer einzigen Reduzierfunktion (Reducer) aufgebaut ist. Obwohl Redux im abstrakten Sinn kein Hook ist, haben seine Prinzipien die Entstehung einiger der bekanntesten React-Hooks für anspruchsvolles Zustandsmanagement gefördert, die in der React-Community sehr beliebt sind.
Dieser Hook hat zwei Haupargumente:
- Eine Funktion, die beschreibt, wie der State infolge einer ausgelösten Aktion verändert wird.
- Der anfängliche State.
useReducer liefert zwei Werte zurück: den aktuellen State und eine Funktion, die Aktionen auslöst, um den State zu aktualisieren.
function counterReducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return (
<>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</>
);
}
Im Gegensatz zu useState, zentralisiert useReducer die Logik und sorgt dafür, dass Komponenten berechenbar bleiben. Sobald die Logik Ihrer Komponente durch zahlreiche useState-Aufrufe komplexer wird, können Sie sie auf useReducer umstellen, was leichter zu warten und zu testen ist.
5. useCallback: Unnötige Neuberechnungen vermeiden
Performance-Optimierung ist ein spannendes Thema in der modernen Webentwicklung, da eine bessere Leistung zu einer verbesserten Benutzererfahrung führt. Mit zunehmender Komplexität Ihrer Anwendung gewinnt dies umso mehr an Bedeutung. Einer der von React bereitgestellten Hooks zur Performance-Optimierung ist useCallback. Damit kann man festlegen, welche Funktionen bei jeder Aktualisierung der Komponente neu erstellt werden sollen, um unnötige Neuberechnungen zu vermeiden.
function ProductPage({ productId, referrer, theme }) {
const handleSubmit = (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}
return (
<div className={theme}>
<ShippingForm onSubmit={handleSubmit} />
</div>
);
}
Im obigen Beispiel erstellen wir eine ProductPage-Komponente, die ein Versandformular anzeigt. useCallback erwartet als ersten Parameter eine Funktion und als zweiten ein Array von Abhängigkeiten. Nehmen wir an, ShippingForm ist eine rechenintensive Komponente, daher haben wir sie mit memo versehen, um unnötige Neuberechnungen zu verhindern, solange sich ihre Props nicht ändern.
import { memo } from 'react';
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
// ...
});
Möchten Sie den heiklen Punkt erfahren? Hier ist er: Da die handleSubmit-Funktion im Körper von ProductPage deklariert wird, wird diese Funktion bei jeder Änderung einer der Props der Komponente neu erstellt.
Wenn der Benutzer also das Thema ändert, erstellt React die Funktion neu, was ein erneutes Rendern der ShippingForm-Komponente auslöst. Um dies zu verhindern, kommt useCallback zur Hilfe.
Dieser Hook liefert eine gememoisierte Version der bereitgestellten Funktion zurück, die nur dann neu erstellt wird, wenn sich eine ihrer Abhängigkeiten ändert.
So binden Sie die handleSubmit-Funktion mit einem useCallback-Hook ein.
const handleSubmit = useCallback((orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails,
});
}, [productId, referrer]);
Nun bleibt diese gememoisierte Version des Callbacks bei jedem erneuten Rendering von ProductPage gleich, solange sich keine der Abhängigkeiten (die Werte im Array) ändert.
Auf diese Weise hilft useCallback, Leistungsprobleme zu vermeiden, die durch unnötige Neuerstellungen und Neurenderings von Kindkomponenten entstehen. Die Komponenten sind auf diese Funktionen angewiesen, weshalb Memoisierung eingesetzt wird.
6. useMemo: Zwischenspeicherung teurer Berechnungen
Ähnlich wie useCallback, verwendet auch der useMemo-Hook eine Memoisierungstechnik, um die Performance in React-Anwendungen zu optimieren. Allerdings wird hier nicht eine Funktion gememoisiert, sondern das Ergebnis einer Berechnung. Sind die Abhängigkeiten unverändert, gibt der Hook bei allen folgenden Renderings den gememoisierten Wert zurück. Andernfalls wird der Wert neu berechnet und zwischengespeichert.
Dieser Hook ist besonders praktisch, wenn unser Code komplexe Aufgaben erledigen muss, etwa das Sortieren einer langen Liste von Elementen.
const sortedItems = useMemo(() => sortItems(veryLongListOfItems), [veryLongListOfItems]);
Memoisierung ist jedoch nicht kostenlos. Daher sollten Sie diesen Hook gemäß den Best Practices von React nur dann verwenden, wenn die Neuberechnung aufwendiger ist als die Memoisierung und es sich lohnt, den vorherigen Wert durch Vergleichen mit dem aktuellen beizubehalten.
7. useDeferredValue: Teure Renderings wirken sofort reaktionsschnell
Die Softwareentwickler müssen oft auf Benutzereingaben mit rechenintensiven Operationen reagieren. Dieser Hook ist neu in React 18 und vermutlich einer der am wenigsten bekannten React-Hooks. Er ermöglicht es Entwicklern, die Berechnung eines Wertes so lange aufzuschieben, bis die wichtigeren Bereiche der Benutzeroberfläche fertig aktualisiert sind. In manchen Fällen ist useDeferredValue eine großartige Methode, um reaktionsschnelle Benutzeroberflächen zu schaffen, die nicht durch langsame Komponenten in weniger wichtigen Bereichen der UI ausgebremst werden.
Sehen wir uns an, wie dieser Hook funktioniert.
import React, { useState, useDeferredValue } from 'react';
const SearchComponent = () => {
const [query, setQuery] = useState('');
const deferredQuery = useDeferredValue(query);
const handleChange = (event) => {
setQuery(event.target.value);
};
return (
<div>
<input
type="text"
value={query}
onChange={handleChange}
placeholder="Type your search query"
/>
<SearchResults query={deferredQuery} />
</div>
);
};
Im obigen Beispiel rendert unsere App zwei UI-Komponenten: ein Eingabefeld und die Ergebnisliste, die auf dem Wert des Eingabefelds basiert. Beim Tippen auf das Eingabefeld erwartet der Nutzer, dass die Zeichen sofort auf dem Bildschirm erscheinen, während das Aktualisieren der Ergebnisse um eine gewisse Zeit verschoben werden kann (was sich tatsächlich „natürlich“ anfühlt).
Durch den Einsatz von useDeferredValue lassen wir React Prioritäten setzen: Das Rendern des Eingabefelds erfolgt sofort, während das Rendern der SearchResults-Komponente auf eine weniger dringende Aktualisierungsphase verschoben wird. Das hilft, eine optimale Tippgeschwindigkeit des Nutzers zu gewährleisten und Verzögerungen beim Rendern der Ergebnisliste zu vermeiden.
8. useTransition: UI-States elegant verwalten
Während useDeferredValue einen Wert verzögert, verzögert useTransition eine State-Aktualisierung. Dadurch können Sie Updates als nicht dringend kennzeichnen, was die Flüssigkeit bei komplexen UI-Übergängen verbessert.
import { useState, useTransition } from "react";
function Gallery() {
const [isPending, startTransition] = useTransition();
const [tab, setTab] = useState("photos");
const handleTabChange = (nextTab) => {
startTransition(() => setTab(nextTab));
};
return (
<>
{isPending && <p>Loading...</p>}
<Tabs current={tab} onChange={handleTabChange} />
</>
);
}
useTransition gibt als ersten Wert ein boolesches „loading“ zurück und als zweiten eine Funktion. Innerhalb dieser Funktion führen wir die State-Aktualisierung durch, die im „Hintergrund“ berechnet wird, ohne die Benutzeroberfläche zu blockieren. Wir können dieses boolesche Feld verwenden, um einen Ladezustand anzuzeigen, falls wir auf diese Aktualisierung warten. Dadurch bekommt der Nutzer eine Rückmeldung, dass etwas passiert, was die Benutzererfahrung verbessert.
Da React erkennen kann, welche State-Veränderungen transitiv sind, kann es zuerst Interaktionen ausführen und so die wahrgenommene Reaktionsfähigkeit in komplexen Benutzeroberflächen behalten.
9. useOptimistic: Nahtlose Benutzererfahrung durch sofortiges Feedback
Als Teil der React 19 Hooks angekündigt, ist useOptimistic eine einfache, aber äußerst spannende Neuerung zur Handhabung optimistischer UI-Aktualisierungen. Die Idee hinter diesem Hook ist sehr einfach: Wir treffen eine optimistische Annahme, aktualisieren die UI sofort und lassen den Server später nachziehen. Sollte etwas schiefgehen, wird ein Rollback durchgeführt.
In den meisten Fällen fühlt sich die optimistische Aktualisierung flüssig an, ohne dass ein Rollback notwendig ist. Daher wird dieser Ansatz häufig für Updates verwendet, die asynchrone Anfragen erfordern.
Früher musste man den State manuell mit useState oder useReducer verwalten. Doch dank des useOptimistic-Hooks können wir dieses Problem nun auf sehr einfache Weise umgehen.
import { useOptimistic } from "react";
function CommentForm({ sendComment }) {
const [optimisticComments, addOptimisticComment] = useOptimistic(
[],
(state, newComment) => [...state, { text: newComment, optimistic: true }]
);
const handleSubmit = async (e) => {
e.preventDefault();
const text = e.target.comment.value;
addOptimisticComment(text);
await sendComment(text); // Simulate network call
};
return (
<>
<form onSubmit={handleSubmit}>
<input name="comment" placeholder="Write a comment..." />
<button type="submit">Send</button>
</form>
<ul>
{optimisticComments.map((c, i) => (
<li key={i}>{c.text}</li>
))}
</ul>
</>
);
}
Dieses Beispiel rendert den Kommentar sofort, noch bevor die Netzwerkanfrage vollständig beendet ist. Sobald der Server antwortet, wird der bestätigte State den optimistischen State ersetzen und so eine nahtlose Benutzererfahrung bieten.
10. Custom Hooks: Die geheime Superkraft
Vergessen Sie nicht, dass Sie eigene React-Hooks verwenden können. Diese sind perfekt, um Logik auf besonders elegante Weise wiederzuverwenden. Sie können auch ein modular aufgebautes Hilfsmittel erstellen, das einige der integrierten Hooks verwendet.
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const saved = localStorage.getItem(key);
return saved ? JSON.parse(saved) : initialValue;
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue];
}
Dieser useLocalStorage-Hook kombiniert Persistenz und State, wie es bei vielen produktiven React-Anwendungen der Fall ist. Sie können speziell für Ihr Projekt zugeschnittene Tools erstellen, indem Sie moderne React-Hooks wie useLayoutEffect, useReducer und useCallback zusammenstellen.
Wie man unterschätzte React-Hooks verwendet
Hier sind einige Tipps, die Sie beim Einsatz der in diesem Artikel vorgestellten React-Hooks beachten sollten:
- Verwenden Sie „unterschätzte“ Hooks nur, wenn es erforderlich ist, um echte Performance- oder Lesbarkeitsprobleme zu lösen.
- Vermeiden Sie die Mischung der Hooks: Halten Sie die Logik in einem Hook zusammen.
- Prüfen Sie stets die Abhängigkeitsarrays auf ihre Korrektheit.
- Verwenden Sie eigene React-Hooks zur Wiederverwendbarkeit und Konsistenz in Ihrer gesamten Anwendung.
- Was die Zukunft betrifft: Bleiben Sie informiert über die Best Practices der React 19 Hooks und Community-RFCs.
Fazit
Viele React-Entwickler wissen nicht, wie vielfältig das Hook-System tatsächlich ist; es gibt jedoch viel mehr als die grundlegenden Hooks. Über das Bekannte hinauszugehen führt zu besseren und leichter verständlichen Lösungen. In diesem Beitrag haben wir eine Liste geheimer React-Hooks vorgestellt, die jeder Entwickler als Teil der React Entwicklung ausprobieren sollte.
Sie haben es gelesen, jetzt sind Sie am Zug. Führen Sie jeweils nur einen Hook ein. Zum Beispiel könnten Sie useLayoutEffect oder useDeferredValue einsetzen und schauen, wie sich die App verhält. Mit der Zeit hilft Ihnen das Erlernen dieser fortgeschrittenen React-Hooks dabei, moderne Apps schneller, reaktionsfreudiger und einfacher zu verwalten.