useMemo & useCallback
Ergebnisse aufheben, statt sie neu auszurechnen
Beide heben etwas zwischen den Durchläufen auf: useMemo ein Ergebnis, useCallback eine Funktion. Beide sind Optimierungen – kein Standardwerkzeug.
Bei jedem Zeichnen läuft deine Komponente komplett neu durch. Normalerweise ist das billig. Manchmal nicht – dann kannst du React sagen: „Solange sich diese Werte nicht ändern, nimm bitte das Ergebnis von vorhin.“ Das nennt man MemoisierungEin Ergebnis aufheben, statt es noch einmal auszurechnen.Nach dem Motto: „Solange sich die Zutaten nicht ändern, bleibt das Ergebnis gleich.“→.
const ergebnis = useMemo(() => teuerRechnen(a, b), [a, b]);
// ▲ Funktion, die den Wert LIEFERT ▲ worauf achten
const fn = useCallback((x) => machWas(x, a), [a]);
// ▲ die Funktion SELBST wird aufgehoben Ein Zettel am Kühlschrank: „Solange a und b gleich bleiben, ist das Ergebnis das hier.“ Ändert sich einer der beiden, wird neu gerechnet – sonst gibt es die alte Antwort.
useCallback(fn, deps) ist exakt dasselbe wie useMemo(() => fn, deps). Es gibt ihn nur, weil man Funktionen so oft aufhebt.
Es gibt genau drei gute Gründe
1. Die Berechnung ist wirklich teuer
(Tausende Einträge sortieren, aufwendig formatieren)
2. Der Wert geht als Prop an eine Komponente mit React.memo
-> ohne gleichbleibende Referenz greift memo nicht
3. Der Wert steht in einer Dependency-Liste (useEffect & Co.)
-> sonst läuft der Effect bei jedem Zeichnen Alles andere macht deinen Code nur länger und schwerer lesbar. useMemo um a + b kostet mehr, als es spart.
function Liste({ items, filter, onWaehlen }) {
// Grund 1: teuer, und das Ergebnis wird angezeigt
const sichtbar = useMemo(
() => items.filter((i) => i.name.includes(filter)).sort(vergleiche),
[items, filter]
);
// Grund 2: geht an eine memo-Komponente
const handleClick = useCallback((id) => onWaehlen(id), [onWaehlen]);
return sichtbar.map((i) => <Zeile key={i.id} item={i} onClick={handleClick} />);
}
const Zeile = memo(function Zeile({ item, onClick }) {
return <li onClick={() => onClick(item.id)}>{item.name}</li>;
}); Eine gleichbleibende Funktion nützt nur, wenn jemand sie vergleicht. Gibst du sie an ein normales <button onClick={…}>, hast du nur Arbeit hinzugefügt. Der Button vergleicht nichts.
Was React.memo macht
React.memo ist eine Hülle um eine Komponente. Sie sagt: „Wenn alle Props dieselben sind wie letztes Mal, spar dir das Zeichnen.“ Der Vergleich ist flacher VergleichReact schaut nur, ob die Werte oberflächlich dieselben sind – nicht, ob zwei Objekte denselben Inhalt haben.→ – er schaut nicht in Objekte hinein.
const Teuer = memo(function Teuer({ daten }) { … });
<Teuer daten={items} /> // ✅ gleich, solange items gleich ist
<Teuer daten={items.filter(Boolean)} /> // ❌ jedes Mal ein neues Array
<Teuer style={{ color: "red" }} /> // ❌ jedes Mal ein neues Objekt Ein Kind als children hereinreichen statt es innen zu zeichnen, verhindert dessen Neuzeichnen ganz ohne memo. Und State möglichst nah an die Stelle zu legen, die ihn braucht, wirkt genauso. Erst Struktur, dann Memo.
- +
Berechnungen, die im ProfilerEin Messwerkzeug in den React DevTools, das zeigt, welche Komponente wie lange gebraucht hat.→ tatsächlich auffallen
- +
Props für Komponenten mit
React.memo - +
Werte, die in einer Dependency-Liste landen
- −
useMemouma + b,items.lengthoder ein kurzesmap - −
Vorsorgliches Memoisieren „für später“
- −
useCallbackfür Handler, die direkt an ein<button>gehen
▸ Genauer erklärt: Kosten, Missverständnisse und der React Compiler optional
Jeder dieser Hooks belegt Speicher und vergleicht bei jedem Durchlauf seine Liste. Bei billigen Berechnungen ist das teurer als die Berechnung selbst.
Das häufigste Missverständnis: „useMemo verhindert, dass die Komponente neu zeichnet.“ Tut es nicht. Die Komponente läuft trotzdem durch – nur die eine Berechnung darin wird übersprungen. Renders verhindert React.memo, und zwar bei Kind-Komponenten.
Die Funktion in useMemo läuft mitten im Zeichnen. Dort darf kein fetch und kein setState stehen – React darf sie verwerfen oder wiederholen.
Der React Compiler (seit 2025 in Version 1.0) setzt diese Optimierung automatisch ein. Wo er läuft, schreibst du useMemo und useCallback kaum noch von Hand. Verstehen musst du sie trotzdem – in bestehendem Code stehen sie überall.
Sitzt das schon?
4 Fragen zu dieser Lektion. Falsche Antworten landen in deiner Statistik.
Jetzt selbst schreiben
Die Liste wird bei jedem Zeichnen neu gefiltert – auch wenn sich nur der Zähler geändert hat. Sorge dafür, dass nur eine Änderung im Suchfeld neu rechnen lässt. Die Zeile „Berechnungen“ zeigt dir, wie oft gerechnet wurde.
- □Filtern nach
anzeigt nur passende Einträge - □Ein Klick auf
Zähler +1löst keine neue Berechnung aus - □Eine Änderung im Suchfeld löst eine neue Berechnung aus
import { useRef, useState } from "react"; const DATEN = ["Banane", "Ananas", "Mandarine", "Kirsche"]; export default function App() { const [filter, setFilter] = useState(""); const [count, setCount] = useState(0); const rechnungen = useRef(0); // TODO: nur neu berechnen, wenn sich filter ändert rechnungen.current += 1; const sichtbar = DATEN.filter((d) => d.toLowerCase().includes(filter.toLowerCase()) ); return ( <div style={{ fontFamily: "system-ui", padding: 16 }}> <input aria-label="filter" value={filter} onChange={(e) => setFilter(e.target.value)} placeholder="filtern …" />{" "} <button onClick={() => setCount((c) => c + 1)}>Zähler +1 ({count})</button> <p data-testid="rechnungen">Berechnungen: {rechnungen.current}</p> <ul> {sichtbar.map((d) => ( <li key={d}>{d}</li> ))} </ul> </div> ); }