</>
{ }
React · Memoization · Performance · Re-Renders · Optimierung
React.memo: wann wirklich nötig
und wann schadet es der Performance?

React.memo, useMemo und useCallback sind die meistmissverstandenen Performance-Tools in React. Sie werden entweder überall eingesetzt – mit der Hoffnung, alles schneller zu machen – oder gar nicht. Beides ist falsch. Memoization hat Kosten: Vergleichsoperationen, Speicher für gecachte Werte und erhöhte Codekomplexität. Nur dort einsetzen, wo gemessene Re-Render-Probleme vorliegen.

15 Min. Lesezeit React.memo · useMemo · useCallback · React Profiler · Referential Equality React 18 · TypeScript · React DevTools

1. Wie React Re-Renders entscheidet

React rendert eine Komponente neu, wenn ihr State sich ändert, ihre Props sich ändern oder ihr Elternteil neu gerendert wird. Der letzte Punkt ist der wichtigste für das Verständnis von React.memo: Wenn eine Elternkomponente rendert, rendert React standardmäßig alle ihre Kinder – unabhängig davon, ob sich deren Props geändert haben. Das ist kein Bug, sondern ein bewusstes Design: Rendering ist günstig, echte DOM-Updates nicht. Der Reconciler vergleicht das neue VDOM mit dem alten und aktualisiert nur tatsächlich geänderte DOM-Knoten.

Das bedeutet: Nicht jeder Re-Render ist ein Problem. React ist dafür optimiert, viele schnelle Renders durchzuführen. Ein Re-Render wird erst dann zum Problem, wenn die Render-Funktion selbst teuer ist – etwa weil sie eine komplexe Berechnung ausführt, eine große Liste filtert oder sortiert – oder wenn er so häufig auftritt, dass der Main Thread blockiert wird. Erst wenn messbare Performance-Probleme vorliegen, ist Memoization das richtige Werkzeug. Präventive Memoization – überall React.memo und useMemo einsetzen, ohne gemessene Probleme – ist kontraproduktiv.

2. React.memo: Funktionsweise und Grenzen

React.memo ist ein Higher-Order-Component, das eine Komponente umschließt und sie nur dann neu rendert, wenn sich ihre Props geändert haben. Der Vergleich erfolgt standardmäßig durch shallow equality: Jede Prop wird mit dem strict-equality-Operator (===) verglichen. Wenn alle Props denselben Referenzwert haben wie beim letzten Render, überspringt React den Render dieser Komponente und gibt das letzte Ergebnis zurück. Wenn sich auch nur eine Prop geändert hat – oder eine neue Referenz hat, auch wenn der Wert logisch gleich ist – rendert die Komponente neu.

React.memo akzeptiert ein optionales zweites Argument: eine custom comparison-Funktion, die entscheidet, ob Props als gleich gelten sollen. Das ermöglicht Deep-Equal-Vergleiche oder selektive Prop-Vergleiche. Diese Funktion hat jedoch eine invertierte Semantik im Vergleich zu shouldComponentUpdate: Sie gibt true zurück, wenn die Komponente nicht neu gerendert werden soll (Props sind gleich), und false, wenn sie neu gerendert werden soll. Diese unintuitiv invertierte Logik ist eine häufige Fehlerquelle. Custom comparators sollten sparsam und nur mit Tests verwendet werden.


import { memo, useMemo, useCallback, useState } from 'react';

// React.memo — only re-renders when props change by reference (shallow equality)
const ExpensiveList = memo(function ExpensiveList({
  items,
  onItemClick,
}: {
  items: string[];
  onItemClick: (item: string) => void;
}) {
  console.log('ExpensiveList rendered'); // should not appear on parent re-renders

  return (
    <ul>
      {items.map(item => (
        <li key={item} onClick={() => onItemClick(item)}>{item}</li>
      ))}
    </ul>
  );
});

function Parent() {
  const [count, setCount] = useState(0);
  const [filter, setFilter] = useState('');

  // WITHOUT useMemo: new array on every parent render → memo is bypassed
  const filteredItems = useMemo(
    () => allItems.filter(item => item.includes(filter)),
    [filter] // stable reference when filter does not change
  );

  // WITHOUT useCallback: new function reference every render → memo bypassed
  const handleItemClick = useCallback((item: string) => {
    console.log('Clicked:', item);
  }, []); // stable reference — no dependencies

  return (
    <div>
      <button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
      <input value={filter} onChange={e => setFilter(e.target.value)} />
      {/* memo works: items and handler have stable references when count changes */}
      <ExpensiveList items={filteredItems} onItemClick={handleItemClick} />
    </div>
  );
}

3. Referential Equality: warum React.memo oft nicht hilft

Die häufigste Ursache dafür, dass React.memo nicht funktioniert: Props mit neuen Referenzen bei jedem Render. In JavaScript erstellt jeder Ausdruck wie [], {} oder () => {} eine neue Referenz – auch wenn Inhalt und Verhalten identisch sind. Wenn eine Komponente ihrem gememoisierten Kind ein Array übergibt, das bei jedem Render neu erstellt wird (<Child items={data.filter(...)}/>), bekommt das Kind jedes Mal eine neue Referenz. React.memo vergleicht die Referenz, nicht den Inhalt – die Komponente rendert trotz React.memo bei jedem Eltern-Render neu.

Dieses Problem betrifft drei Typen von Props: Objekte (style={{color: 'red'}} ist jedes Mal ein neues Objekt), Arrays (inline filter(), map()) und Funktionen (Inline-Arrow-Functions als Event-Handler). Die Lösung für Objekte und Arrays ist useMemo, die Lösung für Funktionen ist useCallback. Aber: Wenn React.memo ohne die Stabilisierung der Props-Referenzen eingesetzt wird, entstehen Kosten ohne Nutzen – der shallow-Vergleich wird durchgeführt und schlägt fehl, weil alle Referenzen neu sind. Schlechter als kein React.memo.

4. useMemo: teure Berechnungen cachen

useMemo cached das Ergebnis einer Berechnung zwischen Renders. Es führt die übergebene Funktion beim ersten Render aus und speichert das Ergebnis. Bei jedem folgenden Render vergleicht es die Abhängigkeiten (Dependency Array) mit dem letzten Render. Wenn alle Abhängigkeiten dieselbe Referenz haben, gibt es den gecachten Wert zurück, ohne die Funktion erneut auszuführen. Wenn sich eine Abhängigkeit geändert hat, führt es die Funktion erneut aus und speichert das neue Ergebnis.

useMemo hat zwei legitime Anwendungsfälle: erstens, teure Berechnungen zu cachen, die bei jedem Render neu ausgeführt werden würden und messbar Zeit kosten – etwa das Filtern einer Liste mit tausenden Einträgen oder die Berechnung eines Graphen-Layouts. Zweitens, stabile Referenzen für Arrays und Objekte zu erzeugen, die als Props an memoisisierte Kindkomponenten übergeben werden. Für alles andere – simple Berechnungen, String-Konkatenationen, Zugriffe auf Objekt-Properties – ist useMemo Overkill: Der Vergleich der Abhängigkeiten und die Verwaltung des Caches kosten mehr als die Berechnung selbst.

5. useCallback: stabile Funktionsreferenzen

useCallback ist im Kern identisch mit useMemo, aber speziell für Funktionen: Statt useMemo(() => () => doSomething(), [dep]) schreibt man kompakter useCallback(() => doSomething(), [dep]). Der Hauptanwendungsfall: Event-Handler-Funktionen, die als Props an memoisisierte Kindkomponenten übergeben werden. Wenn der Handler bei jedem Render neu erstellt wird, umgeht das React.memo. useCallback erzeugt eine stabile Referenz, die sich nur ändert, wenn sich die Abhängigkeiten ändern.

Ein wichtiges Muster im Zusammenhang mit useCallback: Wenn die Funktion auf State zugreift, der sich häufig ändert, muss dieser State in das Dependency Array aufgenommen werden. Das führt dazu, dass sich die Funktionsreferenz bei jeder State-Änderung ändert – womit der Vorteil von useCallback wegfällt. Die Lösung: State-Setter-Funktionen des Updater-Pattern verwenden (setState(prev => prev + 1) statt setState(count + 1)). Das Updater-Pattern braucht keine Abhängigkeit auf den aktuellen State-Wert, weil der aktuelle Wert als Argument übergeben wird.


import { memo, useMemo, useCallback, useState, useRef } from 'react';

// Measuring if memoization is actually helping
function useRenderCount(label: string) {
  const count = useRef(0);
  count.current += 1;
  console.log(`${label} rendered ${count.current} times`);
}

const SortedTable = memo(function SortedTable({
  data,
  onSort,
}: {
  data: { id: number; name: string; value: number }[];
  onSort: (column: string) => void;
}) {
  useRenderCount('SortedTable'); // verify memo is actually working

  return (
    <table>
      <thead>
        <tr>
          <th onClick={() => onSort('name')}>Name</th>
          <th onClick={() => onSort('value')}>Wert</th>
        </tr>
      </thead>
      <tbody>
        {data.map(row => <tr key={row.id}><td>{row.name}</td><td>{row.value}</td></tr>)}
      </tbody>
    </table>
  );
});

function Dashboard({ rawData }: { rawData: { id: number; name: string; value: number }[] }) {
  const [sortColumn, setSortColumn] = useState('name');
  const [theme, setTheme] = useState('light'); // changes should NOT re-render SortedTable

  // useMemo: expensive sort cached by sortColumn, not by theme
  const sortedData = useMemo(
    () => [...rawData].sort((a, b) => a[sortColumn] > b[sortColumn] ? 1 : -1),
    [rawData, sortColumn]
  );

  // useCallback: stable reference — memo works even when theme changes
  const handleSort = useCallback((column: string) => {
    setSortColumn(column);
  }, []); // no deps: uses setSortColumn which is always stable

  return (
    <div data-theme={theme}>
      <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>Theme</button>
      {/* SortedTable should NOT re-render when theme toggles */}
      <SortedTable data={sortedData} onSort={handleSort} />
    </div>
  );
}

6. Re-Renders messen mit React DevTools Profiler

Bevor Memoization eingesetzt wird, muss das Problem gemessen werden. Das wichtigste Werkzeug: React DevTools Profiler. Er zeigt für jeden Render, welche Komponenten neu gerendert wurden und warum – ob wegen Props-Änderung, State-Änderung oder Eltern-Render. Der Flamegraph zeigt die Render-Dauer jeder Komponente. Rote Balken markieren die langsamsten Renders. Die "Why did this render?"-Funktion im Profiler gibt für jede Komponente den Grund an: welche Prop oder welcher State sich geändert hat.

Ein zweites nützliches Tool: Die Browser-Extension react-scan oder das Paket @welldone-software/why-did-you-render. Letzteres gibt bei jedem unnötigen Re-Render – wenn Props referenziell gleich sind, die Komponente aber trotzdem rendert – eine Console-Warnung mit der Ursache aus. Das deckt Fälle auf, wo React.memo eingesetzt wird, aber trotzdem jedes Mal rendert, weil ein Prop eine neue Referenz bekommt. Erst mit diesen Messwerkzeugen kann man fundierte Entscheidungen treffen, wo Memoization wirklich hilft.

7. Alternativen: State nach unten, Composition nach oben

Bevor React.memo eingesetzt wird, sollte geprüft werden, ob strukturelle Alternativen das Re-Render-Problem eleganter lösen. Das erste Muster: State nach unten verschieben (Colocation). Wenn ein State-Update nur einen kleinen Teil der UI betrifft, kann dieser State in die betroffene Komponente verschoben werden. Damit rendert nur diese Komponente bei State-Änderungen neu – nicht die gesamte Elternkomponente mit allen ihren Kindern. Kein React.memo nötig, kein useMemo, keine neue Komplexität.

Das zweite Muster: Composition (children als Prop). Wenn eine Komponente bei jedem Render neu rendert und dabei teure Kindkomponenten mitnimmt, kann die Kindkomponente als children-Prop übergeben werden. Da die Kindkomponente dann vom Großelternteil gerendert wird – der sich nicht ändert – wird sie nicht neu gerendert, auch wenn der Elternteil es tut. Dieses Muster ist besonders effektiv für teure Wrapper-Komponenten wie Modals, Panels und Listencontainer. Es vermeidet Memoization vollständig und macht den Code gleichzeitig lesbarer.

8. Wann Memoization schadet

Memoization schadet in mehreren konkreten Szenarien. Erstens: Wenn Props häufig und zuverlässig wechseln. Wenn eine Komponente bei jedem Render neue Props bekommt – weil der Parent immer neue Referenzen erzeugt – führt React.memo bei jedem Render einen Vergleich durch, der fehlschlägt, und rendert die Komponente trotzdem. Das ist schlechter als kein React.memo, weil der Vergleich eine zusätzliche Operation kostet ohne jemals einen Render zu verhindern.

Zweitens: Wenn useMemo für triviale Berechnungen eingesetzt wird. Eine Berechnung wie const double = useMemo(() => count * 2, [count]) ist schlechter als const double = count * 2. Die Multiplikation kostet Nanosekunden; useMemo verwaltet einen Cache, prüft Abhängigkeiten und hält einen Wert im Speicher. Drittens: Wenn React Compiler in Verwendung ist. Der React Compiler (früher React Forget), der mit React 19 eingeführt wird, analysiert Komponenten statisch und fügt Memoization automatisch und präzise dort ein, wo sie sinnvoll ist. Manuelles React.memo und useMemo werden damit weitgehend überflüssig – und können sogar mit dem Compiler interferieren.

9. Memoization-Strategien im Vergleich

Die Entscheidung, welche Memoization-Strategie eingesetzt wird, hängt vom konkreten Problem ab. Ein strukturierter Vergleich der Optionen zeigt, wann welche Maßnahme sinnvoll ist.

Maßnahme Löst Kosten Wann einsetzen
State Colocation Unnötige Eltern-Renders Keine Immer zuerst prüfen
children Composition Teure Kinder in Wrappern Keine Bei Wrapper-Komponenten
React.memo Renders durch Eltern-Update Shallow Vergleich pro Render Teure Komponente, stabile Props
useMemo Teure Berechnung / Referenz Cache-Verwaltung, Speicher Messbarer Rechenaufwand
useCallback Neue Funktionsreferenz Cache-Verwaltung, Dep-Vergleich Props an memo-Komponenten

Die Reihenfolge der Maßnahmen ist entscheidend: Erst Struktur optimieren (Colocation, Composition), dann messen (Profiler, why-did-you-render), dann gezielt memoisisieren. Memoization ohne vorherige Messung ist Spekulation. Memoization ohne Stabilisierung der Props (useMemo/useCallback) bei gleichzeitiger Verwendung von React.memo ist Mehraufwand ohne Nutzen. Der React Compiler in React 19 nimmt diesen Entscheidungsprozess langfristig ab – bis dahin gilt: erst messen, dann handeln.

Mironsoft

React Performance-Analyse, Memoization-Audit und Re-Render-Optimierung

React-App auf unnötige Re-Renders analysieren?

Wir führen eine professionelle Re-Render-Analyse eurer React-Applikation durch: Profiler-Auswertung, Identifikation teurer Komponenten und gezielte Memoization-Strategie – mit messbaren Vorher/Nachher-Ergebnissen.

Profiler-Analyse

React DevTools Profiler und why-did-you-render für alle kritischen Flows

Strukturoptimierung

State Colocation und Composition vor Memoization — Grundursachen beheben

Gezielte Memoization

React.memo, useMemo und useCallback nur dort, wo gemessener Bedarf besteht

10. Zusammenfassung

React.memo, useMemo und useCallback sind präzise Werkzeuge, keine generellen Beschleuniger. Jede Memoization hat Kosten: Vergleichsoperationen, Cache-Verwaltung und Codekomplexität. Diese Kosten lohnen sich nur, wenn sie durch vermiedene Re-Renders oder gesparte Rechenzeit überwogen werden. Vor jedem Einsatz steht die Messung mit React DevTools Profiler und why-did-you-render. Strukturelle Alternativen – State Colocation und children-Composition – lösen viele Re-Render-Probleme eleganter und ohne zusätzliche Komplexität.

Die drei häufigsten Fehler: React.memo ohne Stabilisierung der Props (useMemo/useCallback), was den Memo-Check immer fehlschlagen lässt. useMemo für triviale Berechnungen, die schneller sind als die Cache-Verwaltung selbst. useCallback mit zu vielen Abhängigkeiten, wodurch die Funktion bei jeder relevanten State-Änderung trotzdem neu erstellt wird. Der React Compiler in React 19 wird diese manuellen Entscheidungen teilweise automatisieren – bis dahin gilt: erst messen, strukturelle Alternativen prüfen, dann gezielt memoisisieren.

React.memo, useMemo, useCallback — Das Wichtigste auf einen Blick

Erst Struktur

State Colocation und children-Composition prüfen bevor memoisisiert wird. Oft lösen sie das Problem ohne jede Komplexität.

Erst messen

React DevTools Profiler und why-did-you-render einsetzen. Nur dort memoisisieren, wo messbare Re-Render-Probleme bestehen.

Props stabilisieren

React.memo ohne stabile Props-Referenzen ist wirkungslos. useMemo für Arrays/Objekte, useCallback für Funktionen als Props.

React Compiler

React 19 Compiler memoisisiert automatisch. Manuelles React.memo wird langfristig überflüssig. Heute: gezielt und gemessen einsetzen.

11. FAQ: React.memo, useMemo und useCallback

1Sollte ich React.memo für alle Komponenten verwenden?
Nein. Shallow-Vergleich kostet bei jedem Render. Ohne gemessene Re-Render-Probleme ist es Overhead ohne Nutzen. Erst messen, dann gezielt bei teuren Komponenten mit stabilen Props einsetzen.
2Warum rendert meine memo-Komponente trotzdem neu?
Props mit neuen Referenzen: Inline-{}, Array-filter()/map() und Arrow-Functions. React.memo vergleicht per Referenz. useMemo für Arrays/Objekte, useCallback für Funktionen stabilisieren.
3useMemo vs. useCallback?
useMemo cached einen Wert (Ergebnis). useCallback cached eine Funktion. useCallback(fn, deps) = useMemo(() => fn, deps). Nur kompaktere Syntax für Funktionen.
4Wann useMemo einsetzen?
Teure Berechnungen (Filtern/Sortieren großer Datensätze). Stabile Referenzen für Props an memo-Komponenten. NICHT für triviale Berechnungen — Overhead überwiegt.
5Was ist State Colocation?
State in die Komponente verschieben, die ihn braucht. Dann rendert nur dieser Teil neu — ohne React.memo und useMemo. Strukturlösung vor Memoization.
6Wie sehe ich warum eine Komponente rendert?
React DevTools Profiler: Aufzeichnung → Flamegraph → "Why did this render?". why-did-you-render: Console-Warnungen bei unnötigen Re-Renders mit Prop-Diff.
7Was ist das children-Composition-Muster?
Teure Kinder als children-Prop übergeben. Da children vom Großelternteil gerendert werden, rendert die teure Komponente nicht neu wenn der Elternteil sich ändert. Kein React.memo nötig.
8useCallback-Abhängigkeiten auf häufig ändernde State vermeiden?
Updater-Muster: setState(prev => prev + 1) statt setState(count + 1). Keine State-Abhängigkeit nötig — Funktion bleibt stabil auch wenn count sich ändert.
9Was ist der React Compiler?
React 19 Compiler fügt Memoization statisch und automatisch ein. React.memo, useMemo und useCallback werden langfristig für die meisten Fälle überflüssig. Heute: manuell und gemessen.
10Wann schadet React.memo der Performance?
Props mit ständig neuen Referenzen: Vergleich schlägt fehl und kostet trotzdem. Günstige Komponenten: Vergleich teurer als Render. Sehr häufig wechselnde Props: Vergleich fast immer erfolglos.