</>
{ }
React · Virtual DOM · Reconciliation · Performance
React Reconciliation:
wie der Virtual DOM wirklich arbeitet

Wer React nur an der Oberfläche kennt, weiß, dass der Virtual DOM „schnell macht". Wer React performant bauen will, muss verstehen, wie der Diffing-Algorithmus entscheidet, was aktualisiert wird – und warum Keys, Komponentenidentität und Fiber-Architektur dabei der entscheidende Faktor sind.

17 Min. Lesezeit Virtual DOM · Fiber · Diffing · Keys · Bailout · memo · useMemo React 18+ · React DevTools Profiler

1. Was der Virtual DOM wirklich ist

Der Virtual DOM ist kein magischer Performance-Layer, sondern eine einfache JavaScript-Datenstruktur: ein Baum aus Plain-Objects, die DOM-Knoten beschreiben. Wenn React render() aufruft, entsteht ein neuer VDOM-Baum. Dieser neue Baum wird mit dem vorherigen verglichen – dieser Prozess heißt Reconciliation. Das Ergebnis ist eine minimale Liste von tatsächlichen DOM-Operationen, die angewendet werden. Der Vorteil gegenüber direktem DOM-Schreiben ist nicht Geschwindigkeit des VDOM an sich, sondern die Bündelung vieler Einzeländerungen in einen kontrollierten Update-Zyklus.

Das ist auch der Grund, warum der Virtual DOM nicht immer schneller ist als direktes DOM-Manipulation: Für einfache, vorhersehbare Updates ist direktes DOM-Schreiben schneller. Der VDOM-Ansatz gewinnt bei komplexen State-Maschinen, wo viele Teile des UI auf denselben State-Wechsel reagieren, weil React diese Updates bündelt, priorisiert und nur die minimal nötigen DOM-Änderungen durchführt. Verstehen, was in diesem Prozess passiert, ist die Voraussetzung dafür, performante React-Apps zu bauen.

2. React Fiber: die Architektur hinter Reconciliation

React Fiber ist die Neuimplementierung des Reconciliation-Algorithmus, die mit React 16 eingeführt wurde. Das Kernproblem des alten Stack-Reconcilers war, dass er synchron und unterbrechbar war: Einmal gestartet, lief ein Render-Zyklus bis zum Ende durch und blockierte den Browser-Thread. Bei großen Component-Trees konnte das zu spürbaren Jank-Effekten führen. Fiber löst das durch ein fein granulares Einheitensystem: Jede Komponente entspricht einem Fiber-Knoten, und die Arbeit kann in kleinen Einheiten aufgeteilt, priorisiert und zwischen Browser-Frames aufgeteilt werden.

Ein Fiber ist eine JavaScript-Objekt-Instanz, die den aktuellen Zustand einer Komponente hält: Props, State, den zugehörigen DOM-Knoten, Referenzen auf Kind-, Geschwister- und Eltern-Fiber sowie den Typ der zugehörigen Komponente. React unterhält zwei Fiber-Bäume gleichzeitig: den current-Baum (das, was aktuell im DOM ist) und den workInProgress-Baum (das, was gerade berechnet wird). Nach einem erfolgreich abgeschlossenen Render-Zyklus werden die Rollen der beiden Bäume getauscht – die sogenannte double buffering-Technik. Das ermöglicht unterbrechbares Rendering ohne den Nutzer je einen halbfertigen Zustand sehen zu lassen.


// Demonstrating React reconciliation behavior with keys
// Without key: React reuses DOM node, just updates text content
function WithoutKey() {
  const [show, setShow] = React.useState(true);
  return (
    <div>
      {show ? <input placeholder="First input" /> : <input placeholder="Second input" />}
      <button onClick={() => setShow(s => !s)}>Toggle</button>
    </div>
  );
  // Problem: same type at same position → React keeps the DOM node
  // Input state (typed text) persists when toggling — unexpected behavior
}

// With key: React unmounts/remounts, DOM node is fresh
function WithKey() {
  const [show, setShow] = React.useState(true);
  return (
    <div>
      {show
        ? <input key="first" placeholder="First input" />
        : <input key="second" placeholder="Second input" />
      }
      <button onClick={() => setShow(s => !s)}>Toggle</button>
    </div>
  );
  // Different keys → different identity → React unmounts old, mounts new
  // Input state resets on toggle — correct behavior
}

3. Der Diffing-Algorithmus: zwei Heuristiken

Einen vollständigen Vergleich zweier Bäume hätte eine zeitliche Komplexität von O(n³). React nutzt stattdessen zwei Heuristiken, die O(n) ermöglichen. Die erste Heuristik: Zwei Elemente unterschiedlichen Typs erzeugen unterschiedliche Bäume. Wenn ein <div> durch ein <section> ersetzt wird, wirft React den gesamten Teilbaum weg und baut ihn neu auf. Das gilt auch für Komponentenwechsel an derselben Position: <ComponentA /> durch <ComponentB /> zu ersetzen bedeutet, dass alle Kind-Komponenten und deren State verloren gehen.

Die zweite Heuristik: Der Entwickler kann mit dem key-Prop stabile Identitäten über Renders hinweg vergeben. Ohne Keys vergleicht React bei Listen die Elemente positionsweise. Wenn ein Element am Anfang eingefügt wird, erkennt React, dass alle folgenden Elemente „verändert" wurden und rendert sie alle neu – obwohl nur ein Element hinzugekommen ist. Mit korrekten Keys erkennt React, welche Elemente gleich geblieben sind, welche verschoben und welche neu sind, und führt nur die minimal nötigen DOM-Operationen durch.

4. Keys: Identität in Listen korrekt setzen

Der key-Prop ist die wichtigste Optimierung bei Listen in React und gleichzeitig die am häufigsten falsch verwendete. Der häufigste Fehler ist, den Array-Index als Key zu verwenden: key={index}. Das funktioniert nur korrekt, wenn die Liste nie sortiert, gefiltert oder in der Reihenfolge verändert wird. Sobald Elemente verschoben werden, führen Index-Keys dazu, dass React Elemente falsch identifiziert – es denkt, ein Element wurde verändert, obwohl es nur verschoben wurde. Das führt nicht nur zu Performance-Problemen, sondern auch zu falschen State-Beibehaltungen.

Der richtige Key ist eine stabile, einzigartige ID aus den Daten selbst: key={item.id}. Wenn die Daten vom Backend kommen, haben sie fast immer eine UUID oder numerische ID. Wenn keine stabile ID existiert, muss eine beim Laden der Daten erzeugt werden – zum Beispiel mit crypto.randomUUID() oder einem Bibliotheks-Generator. Der Key muss nur unter Geschwister-Elementen eindeutig sein, nicht global. Ein Key, der sich zwischen Renders ändert, ist genauso schlimm wie kein Key: React unmountet und remountet die Komponente bei jedem Render.

5. Komponentenidentität und Mount/Unmount

React entscheidet über Identität einer Komponente durch zwei Kriterien: Position im Baum und Typ der Komponente (oder Key, wenn gesetzt). Wenn eine Komponente an derselben Position denselben Typ hat, behält React ihre Instanz und ihren State. Das hat eine wichtige Konsequenz: Wenn eine Komponente bedingt gerendert wird und sich ihre Position im Baum nicht verändert, bleibt der State erhalten – auch wenn die Props sich komplett geändert haben.

Dieses Verhalten ist oft überraschend. Ein häufiges Beispiel: Ein Formular-Komponente wird mit einer anderen userId als Prop gerendert, behält aber den State aus der vorherigen User-Session, weil sie an derselben Position steht. Die Lösung ist ein expliziter key={userId}, der React signalisiert, dass dies eine neue Instanz ist, die von vorne beginnen soll. Das ist ein bewusster Einsatz des Key-Mechanismus nicht für Listen-Optimierung, sondern zur State-Kontrolle.


// Controlling mount/unmount via key for state reset
// Problem: ProfileForm keeps stale state when userId changes
function BadExample({ userId }: { userId: string }) {
  return <ProfileForm userId={userId} />;
  // React sees: same type, same position → keeps instance and state
  // Old form data persists when switching users
}

// Solution: key forces new instance when userId changes
function GoodExample({ userId }: { userId: string }) {
  return <ProfileForm key={userId} userId={userId} />;
  // New key → React unmounts old, mounts fresh instance
  // State resets cleanly on userId change
}

// useEffect-based alternative (when remounting is too expensive)
function AlternativeExample({ userId }: { userId: string }) {
  const [formData, setFormData] = React.useState(getInitialData(userId));

  React.useEffect(() => {
    // Reset state when userId changes without remounting
    setFormData(getInitialData(userId));
  }, [userId]);

  return <ProfileForm data={formData} onChange={setFormData} />;
}

6. Bailout-Mechanismen: wann React das Rendering überspringt

React hat mehrere Mechanismen, um das Neu-Rendern einer Komponente zu überspringen – sogenannte Bailouts. Der einfachste: Wenn useState oder useReducer denselben Wert wie zuvor setzt (nach Object.is-Vergleich), rendert React die Komponente nicht neu. Das ist der Grund, warum setState(sameValue) keinen Re-Render auslöst. Bei Object- und Array-State ist das wichtig zu verstehen: setState(obj) wo obj dieselbe Referenz hat, löst keinen Re-Render aus.

Der zweite Bailout-Mechanismus ist React.memo: Die gesamte Komponente wird mit einem Shallow-Vergleich ihrer Props übersprungen, wenn keine Props sich geändert haben. Der dritte Mechanismus ist die automatische Bailout-Optimierung von React selbst: Wenn eine Eltern-Komponente rendert, rendert React standardmäßig alle Kind-Komponenten ebenfalls – aber React 18 führt automatisches Batching ein, das mehrere State-Updates in einem einzigen Render zusammenfasst. useTransition erlaubt es, Low-Priority-Updates zu markieren, die React zurückstellt, wenn höher-priorisierte Arbeit ansteht.

7. React.memo, useMemo und useCallback korrekt einsetzen

React.memo, useMemo und useCallback werden häufig aus falsch verstandenem Performance-Optimierungsreflex überall eingesetzt. Das ist kontraproduktiv: Jede Verwendung dieser Hooks hat eigene Kosten – Speicherverbrauch für gecachte Werte, CPU-Zeit für Vergleichsoperationen und erhöhte Code-Komplexität. Sie sind keine kostenlose Optimierung, sondern ein Trade-off. Der richtige Einsatz setzt voraus zu verstehen, wann sie tatsächlich helfen.

React.memo hilft, wenn eine Komponente teure Render-Arbeit leistet und oft mit denselben Props aufgerufen wird. useMemo hilft bei teuren Berechnungen, die bei jedem Render neu durchgeführt würden – nicht bei einfachen Objekt-Literals. useCallback hilft, wenn eine Funktion als Prop an eine memo-gewrappte Komponente übergeben wird und stabile Referenz braucht. Beide sind nutzlos ohne React.memo in der Kind-Komponente, da React ohne Memo-Wrapper sowieso neu rendert. Die Faustregel: erst profilen, dann optimieren – nicht umgekehrt.

8. React DevTools Profiler: Flamegraph lesen

Der React DevTools Profiler ist das wichtigste Werkzeug zum Verstehen von Reconciliation in echten Anwendungen. Er zeigt in einem Flamegraph, welche Komponenten bei welchem Render gerendert wurden, wie lange jede Komponente benötigt hat und warum sie gerendert wurde – ob wegen Props-Änderung, State-Änderung oder wegen des Eltern-Elements. Der „Warum"-Aspekt ist dabei am wertvollsten: Er zeigt, ob eine Komponente unnötigerweise rendert, weil eine Funktion oder ein Objekt jedes Mal eine neue Referenz bekommt.

Die Flamegraph-Farben sind intuitiv: Graue Balken bedeuten, dass die Komponente nicht gerendert wurde (Bailout). Farbige Balken zeigen gerenderte Komponenten, wobei intensivere Farben auf längere Render-Zeiten hinweisen. Beim Profilen sollte man immer im Production-Build testen: Development-Mode von React enthält zusätzliche Checks, die das Profil verzerren. Mit Profiler-Component aus React selbst lässt sich das Profiling auch programmatisch in der eigenen App durchführen und Werte ins Monitoring schreiben.

9. Reconciliation-Optimierungen im Vergleich

Reconciliation-Optimierungen funktionieren auf verschiedenen Ebenen. Die folgende Tabelle gibt eine Übersicht, welche Technik welches Problem löst und wann sie eingesetzt werden sollte.

Technik Löst welches Problem Kosten Wann einsetzen
Stabile Keys Falsche Identität in Listen Keine Immer bei Listen – keine Ausnahme
React.memo Unnötige Re-Renders durch Eltern Props-Vergleich bei jedem Render Teure Komponenten mit stabilen Props
useCallback Instabile Funktionsreferenzen als Props Dep-Vergleich + Closure-Speicher Nur mit memo in Kind-Komponente
useMemo Teure Berechnungen pro Render Dep-Vergleich + Speicher für Wert Nachweislich teure Berechnungen
key zum Reset State-Persistenz bei Props-Wechsel Unmount + Mount Wenn useEffect-Reset zu komplex wird

Die wichtigste Erkenntnis aus dieser Tabelle: Stabile Keys kosten nichts und sollten immer verwendet werden. Die anderen Techniken kosten etwas und sollten nur nach Profiling-Analyse eingesetzt werden. Premature Optimization mit memo und useMemo erhöht die Code-Komplexität, ohne nachweisbare Performance-Gewinne zu liefern – und verschleiert echte Bottlenecks.

Mironsoft

React Performance · Reconciliation · Profiling · Optimierung

React-App spürbar langsam? Wir finden die Ursache.

Wir analysieren eure React-App mit dem DevTools Profiler, identifizieren unnötige Re-Renders und implementieren gezielte Reconciliation-Optimierungen – ohne blinde Memo-Überall-Strategie.

Profiling-Session

Flamegraph-Analyse und Identifikation der teuersten Render-Pfade in eurer App

Key-Audit

Index-Keys aufspüren und durch stabile IDs ersetzen – die einfachste Optimierung

Memo-Strategie

React.memo, useMemo und useCallback gezielt und nachweisbar wirksam einsetzen

10. Zusammenfassung

React Reconciliation ist der Prozess, durch den React minimale DOM-Änderungen aus State-Updates berechnet. Die Fiber-Architektur macht diesen Prozess unterbrechbar und priorisierbar. Der Diffing-Algorithmus nutzt zwei Heuristiken: Typ-Wechsel zerstören den Teilbaum, und Keys definieren stabile Identitäten über Renders hinweg. Falsche oder fehlende Keys in Listen sind die häufigste Quelle von Reconciliation-Bugs und unnötigem Re-Rendering.

Bailout-Mechanismen wie React.memo, useMemo und useCallback sind kein kostenloser Geschwindigkeitsbooster, sondern gezielte Werkzeuge, die nach Profiling-Analyse eingesetzt werden sollten. Das Wichtigste für performante React-Apps: Den Profiler öffnen, das tatsächliche Problem verstehen und dann die minimale Optimierung anwenden – stabile Keys, gezielte Memos und klare Komponentengrenzen sind in den meisten Fällen ausreichend.

React Reconciliation — Das Wichtigste auf einen Blick

Virtual DOM

Baum aus JS-Objekten, der mit dem vorherigen Baum verglichen wird. Reconciliation ergibt minimale DOM-Operationen. Nicht immer schneller als direktes DOM.

Fiber-Architektur

Jede Komponente ist ein Fiber-Knoten. Double-Buffering mit current/workInProgress-Baum. Unterbrechbares, priorisiertes Rendering seit React 16.

Keys

Stabile ID aus den Daten – nie den Array-Index. Key-Wechsel → Unmount + Mount. Key-Trick zum State-Reset bei Props-Wechsel einsetzen.

Memo-Strategie

Erst profilen, dann optimieren. React.memo + useCallback zusammen. useMemo nur für nachweislich teure Berechnungen. Keine blinde Memo-Überall-Strategie.

11. FAQ: React Reconciliation und Virtual DOM

1Was ist React Reconciliation?
Vergleich des neuen mit dem vorherigen VDOM-Baum, um minimale DOM-Operationen zu berechnen. Kern des React-Update-Zyklus.
2Was ist React Fiber?
Neuimplementierung des Reconcilers seit React 16. Fein granulare Fiber-Knoten, unterbrechbares und priorisiertes Rendering, Double-Buffering.
3Index als Key problematisch?
Bei Sortierung/Filterung bringt React Elemente falsch durcheinander. Immer stabile ID aus Daten verwenden: key={item.id}.
4Wann React.memo einsetzen?
Erst profilen. Nur bei nachweislich teurer Render-Arbeit und stabilen Props. memo hat eigene Kosten und erhöht Komplexität.
5Was ist ein Bailout?
React überspringt Re-Render: gleicher State-Wert (Object.is), memo ohne Props-Änderung, oder useTransition zurückgestellte Updates.
6State bei Props-Wechsel zurücksetzen?
key={uniqueId} auf der Komponente. Key-Wechsel → Unmount + frischer Mount. Einfacher als useEffect-Reset-Logik.
7Profiler: Flamegraph lesen?
Grau = Bailout (nicht gerendert). Farben = gerendert, intensiver = länger. Hover zeigt Grund: Props/State/Eltern. Im Production-Build profilen.
8useMemo vs. useCallback?
useMemo cached Wert, useCallback cached Funktion. Beide nur sinnvoll wenn gecachte Referenz an memo-Komponente übergeben wird.
9Double Buffering in Fiber?
current und workInProgress-Baum. Nach Render-Zyklus Rollen tauschen. Kein halbfertiger Zustand im DOM sichtbar.
10Kind rendert obwohl Props gleich?
Standard: Eltern-Render löst Kind-Render aus. Nur React.memo verhindert das durch Props-Vergleich. Ohne memo kein Bailout durch Props.