useCallback ist ein React-Hook, der eine memoized Version einer Funktion zurückgibt, die sich zwischen Renderings nicht ändert, bis sich ihre Abhängigkeiten ändern. Im Gegensatz zu einer normalen Funktionsdeklaration innerhalb einer Komponente (die bei jedem Rendering eine neue Funktion erzeugt), stabilisiert useCallback die Referenz auf die Funktion, wodurch unnötige Neurenderungen von mit React.memo optimierten Kindkomponenten verhindert werden. Laut React Documentation (2025) ist useCallback nur in Kombination mit React.memo oder Hooks nützlich, die von einer stabilen Referenz abhängen.
Wichtige Punkte
useCallback ist ein in React 16.8 eingeführter Hook, der eine Funktion memoized: er gibt dieselbe Referenz zurück, bis sich die Abhängigkeiten ändern. Ohne useCallback erzeugt jede Funktionsdeklaration innerhalb einer Komponente bei jedem Rendering ein neues Funktionsobjekt. Bei Primitiven ist dies unbemerkt, aber beim Übergeben solcher Callbacks an mit React.memo optimierte Kindkomponenten verursacht jede neue Referenz eine Neurenderung der Kindkomponente.
Syntaxmäßig ist useCallback äquivalent zu useMemo für eine Funktion: useCallback(fn, deps) ist eine Kurzform von useMemo(() => fn, deps). React speichert die memoized Funktion im internen Speicher des Fiber-Knotens und vergleicht die Abhängigkeiten bei jedem Rendering. Wenn sich die Abhängigkeiten nicht geändert haben (Object.is für jedes Element), wird die vorherige Funktion zurückgegeben.
Laut React Documentation — useCallback (2025) sollte man nicht jede Funktion in useCallback einwickeln. Der Hook hat seinen Preis: Aufrufen des Hooks, Vergleichen der Abhängigkeiten und Speicherallokation für das Abhängigkeiten-Array. Wenn eine Komponente einfach ist und keine tiefen Bäume mit React.memo hat, wird useCallback die Anwendung nur verlangsamen. Optimierung sollte messbar sein, nicht intuitiv.
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
// Stabile Referenz — gleiche Funktion, bis sich deps ändern
const handleClick = useCallback(() => {
setCount(prev => prev + 1);
}, []);
return <Child onClick={handleClick} />;
}
Die Memoization in useCallback basiert auf dem Caching des Ergebnisses des Funktionsaufrufs. React bewahrt den beim ersten Rendering erstellten Closure auf und gibt ihn bei nachfolgenden Renderings zurück, solange die Abhängigkeiten unverändert bleiben. Innerhalb des Fiber-Knotens erzeugt jeder useCallback-Aufruf einen Knoten in der verknüpften Liste der Hooks, in dem die vorherigen Abhängigkeiten und der memoized Wert gespeichert werden.
Der Abhängigkeitsvergleich wird strikt über Object.is durchgeführt — ein oberflächlicher Vergleich ohne tiefe Prüfung von Objekten oder Arrays. Wenn eine Abhängigkeit ein Objekt oder Array ist, wird eine neue Referenz bei jedem Rendering als Änderung betrachtet. Daher sollte das Abhängigkeiten-Array primitive Werte oder stabile Referenzen (z.B. von useRef oder useMemo) enthalten.
Laut React Core Team — Optimization Guide (2024) umfasst die Kosten der Memoization drei Komponenten: Allokation des Abhängigkeiten-Arrays bei jedem Rendering, Durchlaufen und Vergleichen der Elemente über Object.is und potenzieller Garbage-Collection-Overhead beim Neuerstellen. Für eine Komponente mit Hunderten von useCallback-Wrappern kann dies merkbar werden — daher ist Selektivität bei der Verwendung des Hooks entscheidend.
| Szenario | Ohne useCallback | Mit useCallback |
|---|---|---|
| Funktionserstellung | Neu bei jedem Rendering | Gleich bei stabilen deps |
| An React.memo übergeben | Kind rendert neu | Kind rendert nicht neu |
| Im useEffect-Array | Effekt startet neu | Effekt bleibt stabil |
| Overhead | Minimal | Abhängigkeitsvergleich + Speicher |
Es gibt einen weit verbreiteten Mythos, dass useCallback automatisch die Leistung verbessert. In Wirklichkeit verlangsamt useCallback in Isolation (ohne React.memo) die Anwendung sogar geringfügig aufgrund der Kosten des Abhängigkeitsvergleichs. Der Hook bringt nur in drei Szenarien echten Nutzen: Verhindern von Neurenderungen von React.memo-Komponenten, Stabilisieren von Callbacks in useEffect und Übergeben von Callbacks an benutzerdefinierte Hooks, die von Referenzgleichheit abhängen.
Die Regel ist einfach: bis Sie ein Leistungsproblem mit dem React DevTools Profiler entdecken — verwenden Sie useCallback nicht. Das React-Team hat wiederholt betont, dass vorzeitige Optimierung die Wurzel allen Übels ist. Schreiben Sie zuerst sauberen Code ohne Memoization, messen Sie, finden Sie den Engpass im Profiler und fügen Sie dann useCallback dort hinzu, wo es wirklich nötig ist.
// Messbare Optimierung: Child ist in React.memo eingewickelt
const Child = React.memo(({ onClick }) => {
console.log('Child neu gerendert');
return <button onClick={onClick}>Click</button>;
});
function Parent() {
const handleClick = useCallback(() => {
console.log('geklickt');
}, []);
return <Child onClick={handleClick} />;
}
Laut Dan Abramov — Before You memo() (2024) sind über 90% der useCallback-Verwendungen in Open-Source-Projekten überflüssig. Entwickler wickeln jede Funktion „nur für den Fall“ ein, ohne die Wirkung zu messen. Die Alternative: wenn die Kindkomponente schwer ist und ihre Neurenderung teuer — ist React.memo + useCallback gerechtfertigt. Wenn die Kindkomponente leicht ist — ist Neurendern billiger als Abhängigkeitsvergleich.
Das erste Szenario ist React.memo. Wenn eine Kindkomponente in React.memo eingewickelt ist und eine Callback-Funktion als Prop erhält, wird die Kindkomponente ohne useCallback bei jedem Eltern-Rendering neu gerendert, selbst wenn sich ihre eigenen Daten nicht geändert haben. useCallback stabilisiert die Referenz, sodass React.memo die Neurenderung korrekt überspringen kann.
Das zweite Szenario ist useEffect mit einem Callback in den Abhängigkeiten. Wenn eine Funktion an das Abhängigkeiten-Array von useEffect übergeben wird, startet jede neue Referenz den Effekt neu. useCallback stellt sicher, dass die Referenz stabil ist, und der Effekt wird nur ausgeführt, wenn sich die tatsächlichen Daten ändern, nicht bei jedem Rendering. Dies ist besonders wichtig für Subskriptionen und Anfragen.
// useCallback für stabile useEffect-Abhängigkeit
const fetchData = useCallback(async (id) => {
const res = await fetch(`/api/${id}`);
setData(res.data);
}, []); // stabile Referenz, wird nie neu erstellt
useEffect(() => {
fetchData(props.id);
}, [props.id, fetchData]); // Effekt läuft nur, wenn sich props.id ändert
Der Hauptunterschied zwischen useCallback und useMemo ist, was jeder memoized. useCallback memoized eine Funktion: useCallback(fn, deps) gibt fn zurück (dieselbe oder die vorherige Version). useMemo memoized das Ergebnis eines Funktionsaufrufs: useMemo(() => computeExpensive(a, b), [a, b]) gibt den berechneten Wert zurück, keine Funktion.
Technisch gesehen ist useCallback syntaktischer Zucker über useMemo: useCallback(fn, deps) ist äquivalent zu useMemo(() => fn, deps). Diese Syntax existiert nur zur Lesbarkeit — damit der Entwickler klar sieht, dass eine Funktion memoized wird, nicht ein Wert. Es gibt keinen Leistungsunterschied zwischen useCallback und useMemo mit einer Funktion — sie erzeugen identischen Code.
| Hook | Memoized | Syntax | Verwendung |
|---|---|---|---|
| useCallback | Funktion (Referenz) | useCallback(fn, deps) | Callbacks für Kindkomponenten |
| useMemo | Berechnungsergebnis | useMemo(() => value, deps) | Teure Berechnungen, Objektmemoization |
// Diese sind äquivalent:
const handleClick = useCallback(() => doSomething(a, b), [a, b]);
const handleClick = useMemo(() => () => doSomething(a, b), [a, b]);
Der häufigste Fehler ist das sinnlose Einwickeln aller Funktionen in useCallback ohne React.memo auf Kindkomponenten. Wenn eine Kindkomponente nicht in React.memo eingewickelt ist, wird sie bei jedem Eltern-Rendering trotzdem neu gerendert, unabhängig davon, ob sich die Callback-Referenz ändert oder nicht. useCallback ohne React.memo ist Kosten ohne Nutzen.
// ❓ Useless: kein React.memo am Kind
const handle = useCallback(() => doStuff(), []);
<Child onClick={handle} />; // Child rendert ohne React.memo trotzdem neu
// ❓ Stale Closure: fehlende Abhängigkeit
const handle = useCallback(() => {
console.log(count); // count ist immer 0 — stale Closure!
}, []);
// ✅ Richtig: Abhängigkeiten einschließen
const handle = useCallback(() => {
console.log(count);
}, [count]);
Das Problem des stale Closure in useCallback wird gelöst, indem alle verwendeten Variablen in das Abhängigkeiten-Array aufgenommen werden. eslint-plugin-react-hooks mit exhaustive-deps überprüft automatisch, dass alle Variablen aus dem Callback-Rumpf im Array vorhanden sind. Wenn der Callback setState verwendet, das sich zwischen Renderings nicht ändert, kann es sicher in deps aufgenommen werden — React garantiert die Stabilität von setState.
Häufig gestellte Fragen
Nein. useCallback macht nur in drei Fällen Sinn: die Kindkomponente ist in React.memo eingewickelt, die Funktion wird im Abhängigkeiten-Array von useEffect verwendet, oder die Funktion wird an einen benutzerdefinierten Hook übergeben, der von Referenzgleichheit abhängt. In anderen Fällen fügt useCallback Overhead ohne Nutzen hinzu. Das React-Team empfiehlt, zuerst ohne Optimierungen zu schreiben und sie basierend auf Profiling-Ergebnissen hinzuzufügen.
Für einfache Komponenten — eine neue Funktion bei jedem Rendering ist etwas schneller, da useCallback Ressourcen für den Abhängigkeitsvergleich und die Array-Allokation verbraucht. Für Komponenten mit tiefen React.memo-Bäumen gewinnt useCallback, indem es die Neurenderung von Tausenden von Kindelementen verhindert. Messen und vergleichen Sie, statt zu raten — verwenden Sie den React DevTools Profiler für eine objektive Bewertung.
Ja, useCallback funktioniert mit async-Funktionen genauso wie mit synchronen. Der Hook memoized die Funktion selbst, und das Ergebnis (ein Promise) wird bei jedem Aufruf zurückgegeben. Eine async-Funktion innerhalb von useCallback ist ein häufiges Muster für stabile Datenlade-Callbacks, die in useEffect verwendet werden: const fetchData = useCallback(async (id) => {...}, []).
Verwenden Sie React DevTools Profiler — er zeigt, welche Komponenten neu rendern und warum. Für programmatische Überprüfung fügen Sie console.log hinzu oder verwenden Sie useWhyDidYouUpdate — eine Bibliothek, die den Grund für die Neurenderung protokolliert. Die Hauptgründe: eine Prop hat sich geändert (einschließlich Callback-Referenz), der State hat sich geändert oder der Kontext hat sich geändert. Wenn useCallback nicht hilft — überprüfen Sie, ob alle Abhängigkeiten korrekt angegeben sind.
Auch ohne React.memo kann useCallback in Kombination mit useMemo für Kontextwerte nützlich sein. Wenn Sie ein Objekt mit Funktionen an Context.Provider übergeben, wickeln Sie die Objekterstellung in useMemo und jede Funktion in useCallback. Dies verhindert die Neurenderung aller Kontextkonsumenten, wenn sich eine der Funktionen ändert. Aber für die direkte Übergabe von Callbacks in Props ohne React.memo bringt useCallback keinen Nutzen.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch