useMemo() verstehen und anwenden
)
useMemo() ist dafür da, das Ergebnis einer teuren Berechnung zwischen Re-Renders zu cachen, solange sich die Dependencies nicht ändern. Hier die Grundproblematik:
1. Das Grundproblem: Referenzielle Gleichheit
Bei JavaScript muss man zwischen Wertgleichheit und Referenzgleichheit unterscheiden. Bei primitiven Werten wie Strings, Zahlen oder Boolean ist das relativ einfach:
const a = 42;
const b = 42;
console.log(a === b); // trueBei Objekten, Arrays und Funktionen sieht es anders aus:
const a = { name: "Frank" };
const b = { name: "Frank" };
console.log(a === b); // falseObwohl die beiden Objekte denselben Inhalt haben, sind sie zwei verschiedene Objekte.
Man kann sich vorstellen:
a ───────► { name: "Frank" }
b ───────► { name: "Frank" }a und b zeigen auf unterschiedliche Objekte im Speicher.
Deshalb:
a === b // falseDerselbe Objektverweis
Wenn du dagegen ein Objekt kopierst, ohne ein neues Objekt zu erzeugen:
const a = { name: "Frank" };
const b = a;
console.log(a === b); // trueJetzt:
a ─────┐
├──► { name: "Frank" }
b ─────┘Beide Variablen referenzieren dasselbe Objekt.
Das ist referenzielle Gleichheit.
Funktionen und Arrays sind in JavaScript ebenfalls Objekte und unterliegen deshalb demselben Prinzip.
React verwendet an vielen Stellen referenzielle Vergleiche (Shallow Comparison), statt Objekte tief miteinander zu vergleichen (Deep Comparison). Das ist eine wichtige Performanceentscheidung.
Jeder Re-Render erzeugt eine neue Referenz obwohl die Werte oder die Funktionslogik gleich geblieben sind. Das kann zu einigen Problemen führen, die nachfolgend besprochen und gelöst werden. Das Lösungsprinzip ist dabei immer gleich: Wir stabilisieren die Referenz.
2. useMemo() um teure Berechnungen cachen
Das ist der wichtigste eigenständige Anwendungsfall für useMemo().
const sortedProducts = useMemo(() => {
return products
.filter(product => product.available)
.sort((a, b) => a.price - b.price);
}, [products]);Ohne useMemo() würde die Filterung und Sortierung bei jedem Render stattfinden.
Das ist sinnvoll, wenn:
große Datenmengen verarbeitet werden
Sortieren/Filtern aufwendig ist
komplexe Berechnungen stattfinden
die Berechnung häufig unnötig wiederholt würde
3 useMemo() für stabile referenzielle Gleichheit
3.1. Als Props für Kind-Komponenten mit React.memo()
Für ein Objekt-Prop ohne useMemo erzeugt jede Elternkomponente bei jedem Render eine neue Objektreferenz – React.memo() an der Kind-Komponente bringt dann nichts, weil die Prop-Referenz sich "ändert", obwohl die Werte gleich bleiben. Die Kind-Komponente würde erneut rendern weil sich die Props geändert haben. Das ist ein klassischer Fall für useMemo. Hier ein Beispiel um dies zu verhindern:
const user = useMemo(() => ({
id,
name
}), [id, name])
<MemoizedButton user={user} /> // memoisiert mit React.memo()3.2 Als Dependency für Hooks
useMemo() stabilisiert die Referenz auf Objekte. Das ist besonders relevant, wenn das Objekt beispielsweise eine Dependency eines anderen Hooks ist. Also auch bei den Dependencies von useMemo() oder useCallback() selbst, aber auch bei useEffect() und bei Custom Hooks. Bei den Beispielen beschränke ich mich auf useEffect():
const options = useMemo(() => ({
userId,
sort
}), [userId]);
useEffect(() => {
fetchData(options);
}, [options]);in solchen einfachen fällen braucht man kein useMemo();
useEffect(() => {
const options = {
userId,
sort
});
fetchData(options);
}, [date, sort]);Interessanter wird es, wenn das Objekt außerhalb des Effects benötigt wird. Zum Beispiel:
const options = useMemo(() => ({
userId,
sortBy: "date",
limit: 20
}), [userId]);
useEffect(() => {
fetchUsers(options);
}, [options]);
function handleRefresh() {
fetchUsers(options);
}4. Zum vermeiden von Re-Render-Kaskaden bei Context
Das ist ein besonders wichtiger Praxisfall. Angenommen:
const value = {
user,
logout
};
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);Bei jedem Render entsteht ein neues value-Objekt. Das kann dazu führen, dass Context Consumer unnötig aktualisiert werden. Man kann deshalb schreiben:
const value = useMemo(() => ({
user,
logout
}), [user, logout]);
return (
<AuthContext.Provider value={value}>
{children}
</AuthContext.Provider>
);Es geht darum, die Referenz des Context-Wertes stabil zu halten.
5. Sonstige Anwendungsfälle
5.1. Werte für Hooks vorbereiten
Wenn ein Hook mehrere komplexe Dependencies hat, kann man diese auch gemeinsam mit useMemo() memoisieren.
const searchConfig = useMemo(() => ({
query,
filters,
sortBy,
page
}), [query, filters, sortBy, page]);
useEffect(() => {
search(searchConfig);
}, [searchConfig]);5.2. Komplexe abgeleitete Daten
Ein sehr typisches React-Muster:
const statistics = useMemo(() => {
return {
total: orders.length,
completed: orders.filter(o => o.status === "completed").length,
revenue: orders.reduce(
(sum, order) => sum + order.price,
0
)
};
}, [orders]);Dann kannst du überall verwenden:
statistics.total
statistics.completed
statistics.revenuestatt die Berechnungen bei jedem Render erneut durchzuführen.
6. Alternativen zu useMemo()
Hier die Alternativen zu useMemo, geordnet danach, wie oft sie in der Praxis wirklich greifen:
6.1. Berechnung aus der Komponente rausziehen
Wenn die Berechnung keine Props/State braucht, gehört sie gar nicht in die Komponente – dann läuft sie nur beim Modul-Import, nicht bei jedem Render.
// Außerhalb der Komponente – läuft nur einmal
const TAX_RATES = calculateTaxTable(); // teuer, aber statisch
function Invoice({ amount }) {
return <span>{amount * TAX_RATES.standard}</span>;
}6.2. Einfach nicht memoisieren (die meistunterschätzte Option)
Die meisten Berechnungen in React-Komponenten sind trivial billig (ein .map(), ein .filter() über wenige hundert Items). Der Vergleich des Dependency-Arrays bei useMemo kostet selbst auch etwas – bei einer billigen Berechnung ist useMemo oft langsamer als die Berechnung direkt auszuführen.
// Bei kleinen Listen: einfach so, kein useMemo nötig
function TodoList({ todos }) {
const activeTodos = todos.filter(t => !t.done);
return <ul>{activeTodos.map(t => <li key={t.id}>{t.text}</li>)}</ul>;
}Faustregel: Erst mit dem React Profiler messen, ob die Berechnung überhaupt spürbar Zeit kostet, bevor
useMemoreinkommt.
6.3. useRef für Werte, die sich nicht bei jedem Render ändern sollen
Wenn du etwas nur einmal berechnen willst (z. B. eine ID oder ein Objekt, das über die Lebensdauer der Komponente stabil bleiben soll), ist useRef oft treffender als useMemo – React garantiert bei useMemo technisch keine Persistenz zwischen Renders (siehe Punkt 6 unten).
function Component() {
const idRef = useRef(null);
if (idRef.current === null) {
idRef.current = generateUniqueId(); // garantiert nur einmal
}
// ...
}6.4. State place statt abgeleitetem Wert (bei Server-/Async-Daten)
Wenn die "teure Berechnung" eigentlich ein Datenabruf ist, gehört sie nicht in useMemo (das ist synchron), sondern in useEffect + useState, oder besser noch in einen Data-Fetching-Layer wie React Query / SWR, der eigenes Caching mitbringt.
// Falsch: useMemo ist nicht für async gedacht
const data = useMemo(() => fetchData(id), [id]); // liefert eine Promise!
// Richtig:
const { data } = useQuery(['item', id], () => fetchData(id));6.5. Strukturelle Vermeidung: Component Composition / children als Prop
Statt ein Objekt zu memoisieren, das an ein Kind weitergereicht wird, kann man manchmal das Re-Render-Problem umgehen, indem man Komponenten so umbaut, dass das teure Kind gar nicht neu rendert – z. B. indem man es als children-Prop reinreicht, statt es innerhalb der neu-rendernden Elternkomponente zu erzeugen.
// Parent rendert neu, aber ExpensiveChild (als children) nicht,
// weil die JSX-Referenz von außen kommt
function Parent({ children }) {
const [count, setCount] = useState(0);
return (
<div onClick={() => setCount(c => c + 1)}>
{count}
{children}
</div>
);
}
<Parent><ExpensiveChild /></Parent>6.6. React Compiler ab React 19.2 und ab Next.js 16
Der größte strukturelle Umbruch: Der React Compiler (früher "React Forget") analysiert deinen Code ab React 19.2 (seit Oktober 2015) statisch und fügt Memoisierung automatisch ein – du schreibst gar kein useCallback/useMemo mehr manuell.
// Mit React Compiler: kein useCallback nötig,
// der Compiler memoisiert automatisch, wo sinnvoll
function Counter() {
const [count, setCount] = useState(0);
const increment = () => setCount(c => c + 1);
return <button onClick={increment}>+</button>;
}Wichtige Randnotiz zu useMemo speziell (im Gegensatz zu useCallback): React garantiert offiziell nicht, dass ein gecachter useMemo-Wert erhalten bleibt – React darf den Cache verwerfen (z. B. um Memory freizugeben) und die Berechnung beim nächsten Render trotzdem neu ausführen. useMemo ist also strikt eine Performance-Optimierung, kein Mechanismus, auf den man sich für Correctness verlassen darf (z. B. um Referenzgleichheit zu garantieren). Falls du das brauchst, ist useRef der richtige Weg.
Faustregel: Wie bei
useCallbackgilt – erst 1, 2 und 5 als strukturelle Lösung prüfen.useMemolohnt sich konkret nur, wenn die Berechnung nachweislich teuer ist (gemessen, nicht vermutet) UND wirklich bei jedem Render neu laufen würde.
7. Fazit
Der vielleicht wichtigste Punkt für React-Anfänger ist daher:
useMemo()bedeutet nicht einfach „mach meine Komponente schneller“. Es memoisiert einen Wert. Die stabile Referenz dieses Wertes kann anschließend an ganz unterschiedlichen Stellen nützlich sein.
Noch wichtiger ist, warum useMemo() den Wert stabil hält:
useMemo()
│
┌──────────────┴──────────────┐
│ │
▼ ▼
teure Berechnung stabile Referenz
│ │
▼ ┌─────────┼─────────┐
Ergebnis merken │ │ │
▼ ▼ ▼
React.memo Hooks Context