wie der neue Reconciler deine UI flüssig hält
React Fiber ist nicht nur ein internes Implementierungsdetail – es ist die Grundlage dafür, warum React 18 mit Concurrent Mode, useTransition und Suspense Anwendungen fundamental anders rendert als alle Versionen davor. Wer verstehen will, warum manche Updates dringlicher sind als andere und wie React entscheidet, was als nächstes auf den Bildschirm kommt, muss Fiber und sein Prioritätensystem kennen.
Inhaltsverzeichnis
- 1. Was React Fiber wirklich ist
- 2. Der alte Stack-Reconciler und seine Grenzen
- 3. Fiber-Knoten: Arbeit in unterbrechbare Einheiten
- 4. Lanes: das Prioritätensystem von React
- 5. Concurrent Mode aktivieren und verstehen
- 6. useTransition: dringende vs. nicht-dringende Updates
- 7. Suspense und Concurrent Rendering zusammen
- 8. Typische Fehler mit Concurrent Mode
- 9. Legacy vs. Concurrent Mode im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Was React Fiber wirklich ist
React Fiber ist die vollständige Neuentwicklung des React-Reconcilers, die mit React 16 eingeführt wurde und bis heute die Grundlage aller Rendering-Operationen bildet. Der Begriff „Fiber" bezeichnet dabei zweierlei: einerseits die Architektur des neuen Reconcilers als Ganzes, andererseits die einzelnen Datenstrukturen – die Fiber-Knoten – die den Komponentenbaum im Speicher repräsentieren. Wer verstehen will, wie React Fiber und Concurrent Mode funktionieren, muss zunächst das Problem kennen, das Fiber löst: die Blockierung des Main Threads durch synchrones Rendering.
Vor Fiber hat React den gesamten Komponentenbaum in einem einzigen, nicht unterbrechbaren Durchlauf verarbeitet. Bei tiefen Bäumen oder rechenintensiven Komponenten blockierte das den Browser für hunderte Millisekunden – keine Eingaben, keine Animationen, keine visuellen Updates. Fiber löst das, indem es Renderarbeit in kleine Einheiten aufteilt, die der Scheduler einzeln ausführen und jederzeit unterbrechen kann. Das ist die Grundlage, auf der Concurrent Mode und das gesamte Prioritätensystem aufbauen.
2. Der alte Stack-Reconciler und seine Grenzen
Der ursprüngliche Stack-Reconciler in React verarbeitete den Komponentenbaum rekursiv: eine Komponente rendert, ruft ihre Kinder auf, diese rufen ihre Kinder auf, und so weiter bis zu den Blättern des Baums. Diese Rekursion war synchron und konnte nicht unterbrochen werden. Sobald sie begann, musste sie bis zum Ende durchlaufen – der Browser hatte in dieser Zeit keine Möglichkeit, auf Nutzerenteignisse zu reagieren oder einen Frame zu zeichnen. Bei komplexen Applikationen führte das zu messbaren Ruckeln, besonders bei schneller Eingabe in Textfelder, die Listen neu filtern.
Das fundamentale Problem des Stack-Reconcilers war sein Ausführungsmodell: callstack-basierte Rekursion lässt sich von außen nicht unterbrechen. Fiber ersetzt die Rekursion durch eine explizite verknüpfte Liste von Fiber-Knoten und eine Arbeitsschleife, die den Scheduler kooperativ einbeziehen kann. Die Arbeitsschleife fragt nach jedem Fiber-Knoten, ob noch Zeit im aktuellen Frame ist – und gibt andernfalls Kontrolle zurück. Das macht React Fiber zu einem kooperativen, nicht zu einem präemptiven System.
3. Fiber-Knoten: Arbeit in unterbrechbare Einheiten
Jede React-Komponente im Baum entspricht einem Fiber-Knoten – einer JavaScript-Objektstruktur mit Feldern für Typ, Props, State, Effekte und Zeiger auf Kind, Geschwister und Elternteil. Diese verknüpfte Listenstruktur ermöglicht es der Arbeitsschleife, nach jedem Knoten innezuhalten und später exakt dort weiterzumachen, wo sie aufgehört hat. React hält dabei zwei Bäume vor: den aktuellen Baum, der die sichtbare UI repräsentiert, und den Work-in-Progress-Baum, in dem neue Updates verarbeitet werden. Erst wenn der Work-in-Progress-Baum vollständig ist, werden beide atomar vertauscht – das nennt sich „Commit Phase".
Die Render-Phase (auch „Reconciliation Phase") ist unterbrechbar und kann mehrfach wiederholt werden. Wenn React während der Render-Phase ein dringenderes Update entdeckt – etwa eine Tastatureingabe – kann es die laufende Arbeit verwerfen, das dringende Update synchron abarbeiten und danach die unterbrochene Arbeit neu starten. Diese Eigenschaft hat wichtige Konsequenzen für Seiteneffekte: In der Render-Phase darf nichts außerhalb der Komponente verändert werden, weil dieselbe Arbeit mehrfach ausgeführt werden kann. Seiteneffekte gehören ausschließlich in die Commit-Phase via useEffect.
// Understanding fiber work phases — render phase is repeatable, commit is not
import { useState, useEffect, useRef } from 'react';
function FiberPhaseDemo() {
const [count, setCount] = useState(0);
const commitCount = useRef(0);
// Render phase: may run multiple times — no side effects allowed here
const expensiveValue = computeExpensiveValue(count); // pure, idempotent
// Commit phase: runs exactly once after React commits to DOM
useEffect(() => {
commitCount.current += 1;
// Safe to interact with DOM, subscriptions, external systems here
document.title = `Count: ${count} (commits: ${commitCount.current})`;
}, [count]);
return (
<div>
<p>Value: {expensiveValue}</p>
<button onClick={() => setCount(c => c + 1)}>Increment</button>
</div>
);
}
function computeExpensiveValue(n: number): number {
// Must be pure — React Fiber may call render phase multiple times
return n * n;
}
4. Lanes: das Prioritätensystem von React
React Fiber verwendet seit React 18 ein Bitmasken-System namens Lanes, um Updates nach Dringlichkeit zu klassifizieren. Jede Lane ist eine Bitmaske, und mehrere Lanes können kombiniert werden, um Gruppen von Updates darzustellen, die zusammen verarbeitet werden. Synchrone Updates – etwa direkte DOM-Interaktionen oder flushSync-Aufrufe – erhalten die höchste Priorität. Transaktionsupdates via useTransition erhalten niedrigere Priorität. Updates durch setTimeout oder aus Netzwerkantworten landen auf noch niedrigeren Lanes.
Das Lanes-System löst ein Problem, das sein Vorgänger (das Expiration-Time-Modell) hatte: Priorisierung von Gruppen zusammengehöriger Updates. Mit Bitmasken kann React effizient prüfen, welche Updates zur selben Gruppe gehören, welche zusammen verarbeitet werden dürfen und welche dringender sind als andere – alles ohne teure Datenstrukturen. Für Entwickler ist das Lanes-System transparent: Die öffentliche API – useTransition, useDeferredValue und startTransition – abstrahiert das Prioritätssystem vollständig.
5. Concurrent Mode aktivieren und verstehen
Concurrent Mode wird in React 18 durch den neuen Root-Einstiegspunkt aktiviert: createRoot statt render. Das ist kein gradueller Opt-in auf Komponentenebene – wenn createRoot verwendet wird, gilt Concurrent Mode für den gesamten Baum. Der Unterschied zum Legacy-Modus: Im Legacy-Modus ist jedes setState-Aufruf ein synchrones, unterbrechungsfreies Render. Im Concurrent Mode kann React mehrere Updates bündeln, unterbrechen und neu ordnen. Das Bündeln (Automatic Batching) gilt seit React 18 für alle Updates, auch in setTimeout und Promise-Callbacks.
Automatic Batching ist der erste spürbare Effekt von Concurrent Mode in der Praxis: Mehrere State-Updates im selben Event-Handler führen zu einem einzigen Re-Render statt zu mehreren. Das war in React 17 nur innerhalb von React-Event-Handlern der Fall, in timeouts und promises aber nicht. Mit React 18 gilt das Batching überall automatisch. Wer ausnahmsweise synchrones Flushen braucht – etwa wenn ein Update sofort sichtbar sein muss, bevor der nächste Code läuft – kann flushSync aus react-dom verwenden.
// React 18: createRoot enables Concurrent Mode for the entire tree
import { createRoot } from 'react-dom/client';
import { StrictMode } from 'react';
import App from './App';
// Concurrent Mode root — all features (useTransition, Suspense, etc.) available
const root = createRoot(document.getElementById('root')!);
root.render(
<StrictMode>
<App />
</StrictMode>
);
// Automatic batching: both setters cause ONE re-render in React 18
async function handleSave() {
const data = await fetchData();
setLoading(false); // batched
setData(data); // batched — single re-render
}
// Force synchronous flush when needed (e.g. measuring DOM before update)
import { flushSync } from 'react-dom';
flushSync(() => {
setCount(c => c + 1); // renders immediately, before flushSync returns
});
const newHeight = ref.current.getBoundingClientRect().height;
6. useTransition: dringende vs. nicht-dringende Updates
useTransition ist die wichtigste neue API, die Concurrent Mode für Entwickler greifbar macht. Sie trennt UI-Updates in zwei Kategorien: dringend (urgent) und nicht-dringend (transition). Dringende Updates müssen sofort sichtbar sein – Eingaben, Klicks, Scrolling. Nicht-dringende Updates können warten – Suchergebnisse, gefilterte Listen, Seitenübergänge. Wenn ein dringendes Update während eines laufenden Transition-Updates eintrifft, unterbricht React das Transition-Update, verarbeitet das dringende Update synchron und startet das Transition-Update danach neu.
Das Muster ist einfach: Der State-Setter für die teure Operation wird in startTransition verpackt. Der isPending-Boolean aus useTransition ist während der Verarbeitung true und kann genutzt werden, um einen Lade-Zustand im UI anzuzeigen – ohne einen eigenen loading-State. Das Ergebnis: Die Texteingabe bleibt responsiv, auch wenn dahinter eine teure Filterfunktion über 10.000 Datensätze läuft. Die Liste aktualisiert sich, sobald React Zeit hat – ohne den Main Thread zu blockieren.
import { useState, useTransition, useDeferredValue } from 'react';
// Pattern 1: useTransition for explicit urgent vs. non-urgent split
function SearchPage() {
const [query, setQuery] = useState('');
const [results, setResults] = useState<string[]>([]);
const [isPending, startTransition] = useTransition();
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
// Urgent: update input immediately
setQuery(e.target.value);
// Non-urgent: filtering can wait, React may interrupt and restart
startTransition(() => {
setResults(filterLargeDataset(e.target.value));
});
}
return (
<div>
<input value={query} onChange={handleChange} placeholder="Suchen…" />
{isPending && <span>Filtern…</span>}
<ResultList items={results} />
</div>
);
}
// Pattern 2: useDeferredValue — defers derived value without explicit transition
function DeferredList({ query }: { query: string }) {
const deferredQuery = useDeferredValue(query);
const isStale = deferredQuery !== query;
return (
<div style={ { opacity: isStale ? 0.6 : 1 } }>
<ExpensiveList filter={deferredQuery} />
</div>
);
}
7. Suspense und Concurrent Rendering zusammen
Suspense existiert seit React 16.6 für Code-Splitting, aber seine volle Wirkung entfaltet es erst mit Concurrent Mode. Ohne Concurrent Mode rendert Suspense-Fallback synchron und blockiert den gesamten Baum, bis die suspendierte Komponente aufgelöst ist. Mit Concurrent Mode kann React während des Wartens weiter andere Arbeit leisten – es rendert einen alternativen Baum im Hintergrund und zeigt ihn erst an, wenn er bereit ist. Das verhindert, dass Ladebalken flackern, wenn Daten sehr schnell ankommen.
Die Kombination von useTransition und Suspense ist besonders mächtig für Seitennavigation: Wenn ein Transition-Update eine Komponente zum Suspendieren bringt, hält React die aktuelle Seite sichtbar und zeigt isPending als true, bis der neue Inhalt bereit ist – kein Layout-Flash, kein Skeleton zwischen zwei echten Inhalten. Erst wenn das Rendering des neuen Inhalts abgeschlossen ist, wird gewechselt. Dieses Muster heißt „Concurrent Rendering with Suspense" und ist die Grundlage für React Router v6.4+ und Next.js App Router Streaming.
8. Typische Fehler mit Concurrent Mode
Der häufigste Fehler bei der Migration zu Concurrent Mode: Seiteneffekte in der Render-Phase. Wenn eine Komponente während des Renderns externe Variablen ändert, Netzwerkanfragen auslöst oder console.log-Aufrufe mit Nebeneffekten ausführt, führt das zu mehrfachen unerwarteten Ausführungen, weil die Render-Phase wiederholt werden kann. React StrictMode in der Entwicklungsumgebung verdoppelt Render-Aufrufe absichtlich, um genau solche Probleme sichtbar zu machen. In der Produktion passiert das nur, wenn React wirklich unterbricht und neu startet.
Ein zweiter häufiger Fehler: externe Stores, die React nicht kennt. Wenn eine Komponente einen externen Store liest und dieser sich zwischen dem Render des Elternteils und dem Render des Kindes ändert, sehen Eltern und Kind inkonsistente Daten – ein sogenannter Tearing-Fehler. Die Lösung ist useSyncExternalStore, das React mitteilt, wie es den Store abonnieren und konsistent lesen soll. Libraries wie Zustand, Jotai und Redux Toolkit haben das bereits intern implementiert.
import { useSyncExternalStore } from 'react';
// Correct pattern: useSyncExternalStore prevents tearing with external stores
function useWindowWidth() {
return useSyncExternalStore(
// subscribe: React calls this to register a change listener
(callback) => {
window.addEventListener('resize', callback);
return () => window.removeEventListener('resize', callback);
},
// getSnapshot: must return same value if nothing changed (stable reference)
() => window.innerWidth,
// getServerSnapshot: for SSR environments
() => 1024
);
}
// Anti-pattern: reading mutable external variable in render (causes tearing)
let externalState = { count: 0 }; // shared mutable state
function BrokenComponent() {
// WRONG: externalState may change between parent and child render
return <div>{externalState.count}</div>;
}
// StrictMode doubles render calls in development to expose side effects
// If your component logs twice — it has unintentional side effects in render
9. Legacy vs. Concurrent Mode im Vergleich
Die Unterschiede zwischen Legacy-Modus (ReactDOM.render) und Concurrent Mode (createRoot) betreffen nicht nur interne Implementierungsdetails, sondern das beobachtbare Verhalten in mehreren Dimensionen. Bei der Entscheidung, ob eine Migration lohnt, helfen konkrete Vergleiche der betroffenen Szenarien.
| Merkmal | Legacy Mode (ReactDOM.render) | Concurrent Mode (createRoot) | Auswirkung |
|---|---|---|---|
| Rendering | Synchron, nicht unterbrechbar | Interleaved, unterbrechbar | Keine Blockierung des Main Threads |
| Batching | Nur in Event-Handlern | Überall automatisch | Weniger unnötige Re-Renders |
| useTransition | Nicht verfügbar | Vollständig unterstützt | Responsivität bei teuren Updates |
| Suspense | Nur Code-Splitting, keine Daten | Daten + Lazy + Streaming | Nahtlose Ladeübergänge |
| StrictMode | Effekte einmal | Effekte doppelt (Dev) | Seiteneffekte in Render sichtbar |
Die Migration von Legacy zu Concurrent Mode erfordert in der Regel drei Schritte: ReactDOM.render durch createRoot ersetzen, externe Stores auf useSyncExternalStore umstellen und Render-Phase-Seiteneffekte identifizieren und in useEffect verschieben. Libraries, die noch nicht für Concurrent Mode geprüft sind, können durch Tearing-Probleme auffallen – ein Blick in das Changelog der verwendeten State-Management-Library lohnt sich vor der Migration.
Mironsoft
React Performance, Concurrent Mode Migration und Frontend-Architektur
React-Applikation auf Concurrent Mode migrieren?
Wir analysieren eure React-Codebasis auf Concurrent-Mode-Kompatibilität, identifizieren Tearing-Risiken und begleiten die Migration von Legacy-Mode zu createRoot – inklusive Prüfung aller externen State-Management-Libraries.
Kompatibilitätsaudit
Render-Phase-Seiteneffekte, externe Stores und Suspense-Grenzen prüfen
Migration
createRoot, useSyncExternalStore und useTransition schrittweise einführen
Performance-Messung
React DevTools Profiler und Interaction Tracing vor/nach Migration auswerten
10. Zusammenfassung
React Fiber ist die Grundlage, auf der alle modernen React-Features aufbauen. Indem der Reconciler Arbeit in unterbrechbare Fiber-Knoten aufteilt und ein Lanes-basiertes Prioritätensystem verwendet, kann React dringende Updates priorisieren, ohne nicht-dringende zu blockieren. Concurrent Mode via createRoot schaltet dieses System für den gesamten Baum frei. Automatic Batching reduziert Re-Renders überall, nicht nur in Event-Handlern. useTransition und useDeferredValue geben Entwicklern direkte Kontrolle über die Priorisierung von UI-Updates.
Die Migration lohnt sich vor allem bei Applikationen mit teuren Render-Operationen, langen Listen oder häufigen State-Updates durch Nutzeingaben. Wer einen externen Store verwendet, muss prüfen, ob er useSyncExternalStore unterstützt, um Tearing zu vermeiden. React StrictMode ist in Concurrent-Mode-Projekten besonders wertvoll: Er macht Render-Phase-Seiteneffekte durch doppelte Ausführung sichtbar, bevor sie in der Produktion zu schwer debuggbaren Problemen werden.
React Fiber & Concurrent Mode — Das Wichtigste auf einen Blick
Fiber-Architektur
Arbeit in unterbrechbare Fiber-Knoten aufgeteilt. Render-Phase wiederholbar, Commit-Phase atomar. Lanes ordnen Updates nach Dringlichkeit.
createRoot aktivieren
Concurrent Mode gilt für den gesamten Baum. Automatic Batching überall. flushSync für synchrones Flushen bei Bedarf.
useTransition & Deferred
Dringende und nicht-dringende Updates trennen. isPending für Ladezustand ohne eigenen State. useDeferredValue als Alternative.
Tearing vermeiden
Externe Stores mit useSyncExternalStore integrieren. StrictMode doppelte Renders enthüllen Render-Phase-Seiteneffekte frühzeitig.