automatisches Memoizing erklärt
Manuelles Memoizing mit useMemo und useCallback ist fehleranfällig, schwer wartbar und wird häufig falsch eingesetzt. Der React Compiler analysiert Komponentencode zur Build-Zeit und fügt automatisch die richtigen Memoizing-Wrapper ein – ohne dass Entwickler Dependencies-Arrays verwalten oder entscheiden müssen, wann etwas gecacht werden soll. Wie das funktioniert und was es bedeutet.
Inhaltsverzeichnis
- 1. Das Memoizing-Problem in React vor dem Compiler
- 2. Was der React Compiler genau tut
- 3. Die Rules of React: was der Compiler voraussetzt
- 4. React Compiler aktivieren: Babel, SWC und Vite
- 5. eslint-plugin-react-compiler: Verstöße finden
- 6. useMemo und useCallback: vorher und nachher
- 7. Grenzen des React Compilers: was er nicht kann
- 8. React Compiler in bestehenden Codebases einführen
- 9. Manuelles Memoizing vs. React Compiler im Vergleich
- 10. Zusammenfassung
- 11. FAQ
1. Das Memoizing-Problem in React vor dem Compiler
Manuelles Memoizing mit useMemo und useCallback ist einer der größten Quellen von Bugs und technischer Schuld in React-Codebases. Das Problem hat drei Dimensionen: Erstens ist Overuse häufig – useMemo wird für einfache Berechnungen eingesetzt, wo der Overhead des Memoizings größer ist als der Nutzen. Zweitens ist Underuse häufig – teure Berechnungen oder Objekte, die als Props übergeben werden und Kindkomponenten unnötig re-rendern, werden nicht gecacht. Drittens sind stale Dependencies ein klassischer Bug: das Dependency-Array von useMemo oder useCallback ist unvollständig, der gecachte Wert veraltete und die Komponente verhält sich inkonsistent.
Der React Compiler – intern bei Meta als "React Forget" entwickelt – löst alle drei Dimensionen gleichzeitig: Er analysiert den Code statisch und entscheidet, was gecacht werden muss, was nicht und welche Dependencies korrekt sind. Entwickler müssen keine Dependencies-Arrays mehr pflegen, keine Entscheidungen über Memoizing-Würdigkeit mehr treffen und keine Bugs durch stale Closures mehr debuggen. Das Ergebnis ist eine Codebase, die konsistent performant ist – ohne manuellen Optimierungsaufwand.
2. Was der React Compiler genau tut
Der React Compiler ist ein Build-Zeit-Compiler, kein Runtime-System. Er analysiert den AST (Abstract Syntax Tree) der React-Komponenten zur Build-Zeit und transformiert den Code: Er identifiziert Werte und Funktionen, die zwischen Renders stabil bleiben können – weil ihre Inputs sich nicht geändert haben – und wickelt diese automatisch in den äquivalenten Memoizing-Code ein. Das generierte JavaScript ist äquivalent zu manuellem useMemo und useCallback, aber präziser: Der Compiler sieht den gesamten Scope und kann feingranulärer entscheiden, welche Teilausdrücke gecacht werden können.
Wichtig zu verstehen: Der React Compiler generiert keinen anderen React-Code, er optimiert den bestehenden. Eine Komponente, die ohne Compiler korrekt funktioniert, funktioniert mit Compiler genauso – nur potentiell mit weniger Re-Renders. Der Compiler fügt kein neues Laufzeitverhalten ein und ändert keine Semantik. Er ist rein additiv in Bezug auf Optimierungen. Das macht ihn sicher für den Einsatz in bestehenden Codebases, sofern der Code die Rules of React einhält – was die zentrale Voraussetzung ist.
// BEFORE React Compiler: manual memoizing required
import { useMemo, useCallback, memo } from 'react';
const ProductCard = memo(function ProductCard({
product,
onAddToCart,
}: {
product: Product;
onAddToCart: (id: string) => void;
}) {
// Developer must manually decide: is this expensive enough for useMemo?
const discountedPrice = useMemo(
() => product.price * (1 - product.discountRate),
[product.price, product.discountRate]
);
// Developer must manually wrap callback to keep reference stable
const handleAddToCart = useCallback(
() => onAddToCart(product.id),
[onAddToCart, product.id]
);
return (
<div>
<h3>{product.name}</h3>
<p>{discountedPrice.toFixed(2)} €</p>
<button onClick={handleAddToCart}>In den Warenkorb</button>
</div>
);
});
// AFTER React Compiler: no useMemo, no useCallback, no memo() needed
// The compiler analyzes the component and adds memoizing automatically
function ProductCard({
product,
onAddToCart,
}: {
product: Product;
onAddToCart: (id: string) => void;
}) {
// Compiler sees: discountedPrice depends only on product.price and product.discountRate
// It automatically caches this — no manual useMemo needed
const discountedPrice = product.price * (1 - product.discountRate);
// Compiler sees: this function only depends on onAddToCart and product.id
// It wraps it in equivalent of useCallback automatically
const handleAddToCart = () => onAddToCart(product.id);
return (
<div>
<h3>{product.name}</h3>
<p>{discountedPrice.toFixed(2)} €</p>
<button onClick={handleAddToCart}>In den Warenkorb</button>
</div>
);
}
3. Die Rules of React: was der Compiler voraussetzt
Der React Compiler setzt voraus, dass der Code die "Rules of React" einhält – ein Set von Invarianten, die das React-Komponentenmodell definieren. Die wichtigsten: Komponenten und Hooks müssen bei gleichen Inputs gleiche Outputs produzieren (Referential Transparency / Idempotenz). State und Props dürfen nie direkt mutiert werden – kein props.list.push(item), kein state.value = newValue. Hooks dürfen nicht bedingt aufgerufen werden – kein if (condition) { useState() }. Side Effects in Render-Funktionen sind verboten – alles außerhalb von useEffect.
Warum diese Regeln für den React Compiler so wichtig sind: Der Compiler baut sein Memoizing-Modell auf der Annahme auf, dass eine Funktion bei denselben Inputs dasselbe Ergebnis produziert. Wenn eine Komponente direkt mutiert oder globale State-Änderungen in der Render-Funktion vornimmt, kann der Compiler nicht mehr sicher entscheiden, wann Ergebnisse gecacht werden können. Er überspringt dann die Optimierung für diese Komponente – oder, bei kritischen Verletzungen, gibt zur Build-Zeit eine Warnung aus. Das ESLint-Plugin eslint-plugin-react-compiler prüft den Code auf diese Verstöße, bevor der Compiler aktiviert wird.
4. React Compiler aktivieren: Babel, SWC und Vite
Der React Compiler wird als Build-Zeit-Plugin installiert: für Babel-basierte Setups als babel-plugin-react-compiler, für SWC-basierte Setups (Next.js 15+ verwendet SWC) über die Next.js-Konfiguration direkt. In Vite-Projekten mit dem @vitejs/plugin-react wird der Compiler als Babel-Plugin in der Vite-Konfiguration übergeben. Die Installation ist standardisiert und in wenigen Konfigurationszeilen erledigt. Der Compiler ist per Default opt-in für alle Komponenten – mit der Option, einzelne Komponenten mit einem Pragma-Kommentar (// @disableReactCompiler) auszuschließen.
Die schrittweise Einführungsstrategie für den React Compiler in bestehenden Projekten: Erst eslint-plugin-react-compiler installieren und alle Verstöße dokumentieren. Dann systematisch fixen – beginnen mit den einfachsten Fällen (direkte Mutationen, fehlende Hooks-Regeln). Danach den Compiler im "Annotation-Only"-Modus aktivieren, in dem er nur Probleme meldet, aber noch keine Optimierungen einfügt. Erst wenn alle Lint-Fehler behoben sind, den Compiler im vollen Modus aktivieren. Dieser Prozess stellt sicher, dass keine bestehende Funktionalität durch den Compiler verändert wird.
// babel.config.js — enabling React Compiler as Babel plugin
module.exports = {
plugins: [
// React Compiler must be listed BEFORE other JSX transforms
['babel-plugin-react-compiler', {
// Optional: only compile specific files during migration
// sources: (filename) => filename.indexOf('src/') !== -1,
// Target React version — ensures correct memoizing semantics
target: '19',
}],
'@babel/plugin-transform-react-jsx',
],
};
// next.config.js — enabling React Compiler in Next.js 15+
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
reactCompiler: true,
// Or with options:
// reactCompiler: {
// compilationMode: 'annotation', // only compile files with "use memo" pragma
// },
},
};
module.exports = nextConfig;
// vite.config.ts — Vite with React Compiler via Babel plugin
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [
react({
babel: {
plugins: [
// Pass React Compiler as Babel plugin to the React Vite plugin
['babel-plugin-react-compiler', { target: '19' }],
],
},
}),
],
});
// Per-component opt-out: add pragma comment to exclude from compilation
// 'use no memo'; // at top of file or component
function LegacyComponent() {
// This component will NOT be optimized by React Compiler
return <div>Legacy code not yet compliant with Rules of React</div>;
}
5. eslint-plugin-react-compiler: Verstöße finden
eslint-plugin-react-compiler ist das empfohlene Werkzeug, um Code vor der Aktivierung des React Compilers auf Verstöße gegen die Rules of React zu prüfen. Das Plugin analysiert denselben Code wie der Compiler selbst und meldet genau die Stellen, die der Compiler nicht optimieren kann oder die zu falschem Verhalten führen würden. Die häufigsten Verstöße: direkte Mutation von State-Objekten (state.items.push(x)), Bedingungen vor Hook-Aufrufen, Verwendung von Variablen aus dem äußeren Scope in einer Art, die Referenztransparenz verletzt, und Side Effects in Render-Funktionen außerhalb von useEffect.
Das Plugin kann auch im "Strikten Modus" betrieben werden, der alle potenziell problematischen Muster meldet – nicht nur die, die den React Compiler direkt blockieren. Das eignet sich für neue Projekte, die von Anfang an Compiler-kompatiblen Code schreiben wollen. Für bestehende Projekte ist der "Warn"-Modus sinnvoller, der nur die tatsächlichen Compiler-Blocker als Fehler meldet. Das ESLint-Plugin ist damit der erste Schritt in jeder React-Compiler-Einführung und gibt einen klaren Arbeitsplan: alle gemeldeten Fehler fixen, dann den Compiler aktivieren.
6. useMemo und useCallback: vorher und nachher
Was passiert mit bestehendem useMemo und useCallback-Code nach der Aktivierung des React Compilers? Der Compiler respektiert und behält existierende manuelle Memoizing-Aufrufe – er überschreibt sie nicht. Das bedeutet: Bestehender Code funktioniert weiterhin, und der Compiler optimiert zusätzlich die Teile, die noch nicht manuell gecacht sind. Langfristig können manuelle useMemo- und useCallback-Aufrufe in Compiler-optimierten Codebases als Hinweis behandelt werden, dass dieser Code früher ohne Compiler problematisch war – und schrittweise entfernt werden.
Der React Compiler macht auch React.memo an vielen Stellen überflüssig. React.memo verhindert Re-Renders einer Komponente, wenn sich ihre Props nicht geändert haben – aber nur wenn die Props-Objekte referenziell stabil sind. Der Compiler sorgt dafür, dass Objekte und Funktionen, die als Props übergeben werden, referenziell stabil sind, wenn ihre Inputs sich nicht geändert haben. Das macht React.memo an vielen Stellen redundant – der Compiler optimiert auf einer granulareren Ebene.
7. Grenzen des React Compilers: was er nicht kann
Der React Compiler hat klare Grenzen, die verstanden werden müssen, um realistische Erwartungen zu haben. Er optimiert nur regelkonformen Code: Jede Komponente, die gegen die Rules of React verstößt, wird vom Compiler übersprungen oder es wird eine Build-Warnung generiert. Das bedeutet, dass eine Legacy-Codebase mit vielen Verstößen von der Aktivierung des Compilers kaum profitiert, bis die Verstöße behoben sind. Der Compiler ist kein magisches Werkzeug, das unsauberem Code Performance gibt – er ist ein Werkzeug für sauberen Code, das Performance automatisch hinzufügt.
Eine weitere Grenze: Der React Compiler analysiert Code-Grenzen zur Build-Zeit. Er kann keine dynamischen Laufzeit-Entscheidungen optimieren. Wenn eine Komponente beispielsweise abhängig von einer externen Mutable-Datenstruktur (ein Array, das direkt mutiert wird) rendert, kann der Compiler nicht sehen, dass sich der Wert geändert hat – und könnte fälschlicherweise gecachte Werte zurückgeben. Deshalb ist die Unveränderlichkeit von State und Props nicht nur ein Stilprinzip, sondern eine funktionale Voraussetzung für korrekte Compiler-Optimierungen.
// eslint.config.js — configuring eslint-plugin-react-compiler
import reactCompiler from 'eslint-plugin-react-compiler';
export default [
{
plugins: {
'react-compiler': reactCompiler,
},
rules: {
// Report all Rules of React violations as errors
'react-compiler/react-compiler': 'error',
},
},
];
// Examples of Rules of React violations — the compiler will skip these components
// VIOLATION 1: Direct mutation of state or props
function MutatingComponent({ items }: { items: string[] }) {
// Direct mutation: items.push modifies the prop directly
// React Compiler cannot safely memoize this component
items.push('new item'); // ❌ Rules of React violation
return <ul>{items.map(i => <li>{i}</li>)}</ul>;
}
// CORRECT: return a new array instead of mutating
function NonMutatingComponent({ items }: { items: string[] }) {
const displayItems = [...items, 'new item']; // ✅ new reference, safe to cache
return <ul>{displayItems.map(i => <li>{i}</li>)}</ul>;
}
// VIOLATION 2: Conditional hook call
function ConditionalHookComponent({ isAdmin }: { isAdmin: boolean }) {
if (isAdmin) {
const [count, setCount] = useState(0); // ❌ Hook inside condition
return <div>{count}</div>;
}
return <div>No access</div>;
}
// CORRECT: always call hooks unconditionally, use them conditionally
function CorrectHookComponent({ isAdmin }: { isAdmin: boolean }) {
const [count, setCount] = useState(0); // ✅ always called
if (!isAdmin) return <div>No access</div>;
return <div>{count}</div>;
}
8. React Compiler in bestehenden Codebases einführen
Die Einführung des React Compilers in einer bestehenden Codebase ist ein strukturierter Prozess, der in Phasen durchgeführt werden sollte. Phase 1: eslint-plugin-react-compiler im Warn-Modus installieren und alle gemeldeten Verstöße dokumentieren. Das gibt einen vollständigen Überblick über den Aufwand. Phase 2: Verstöße nach Häufigkeit und Kritikalität priorisieren. Direkte State-Mutationen sind typischerweise die häufigsten und gleichzeitig wichtigsten zu fixen. Phase 3: Fixes systematisch einführen, beginnend mit den einfachsten Komponenten.
Phase 4: Den React Compiler im "Annotation-Only"-Modus aktivieren – in diesem Modus kompiliert er nur Dateien, die explizit mit dem 'use memo'-Pragma markiert sind. Das ermöglicht eine schrittweise Einführung: Erst einzelne, bereits bereinigte Dateien kompilieren, testen, dann weitere hinzufügen. Phase 5: Nachdem alle Dateien sauber sind und Tests bestehen, den Compiler im Full-Mode aktivieren. Performance-Profiling vor und nach dem Compiler gibt quantitative Daten über den tatsächlichen Gewinn. Typische Verbesserungen: 10–30% weniger Re-Renders in komplexen Komponentenbäumen.
9. Manuelles Memoizing vs. React Compiler im Vergleich
Der direkte Vergleich zeigt, was sich mit dem React Compiler tatsächlich verändert – nicht nur technisch, sondern auch im Entwicklungsalltag.
| Aspekt | Manuelles Memoizing | React Compiler | Gewinn |
|---|---|---|---|
| Dependencies | Manuell pflegen, stale-Bugs möglich | Compiler analysiert automatisch | Keine stale-Closure-Bugs durch deps |
| Granularität | Pro Hook-Aufruf, oft zu grob | Teilausdrücke, sehr fein granular | Präzisere Optimierungen |
| Overuse | Häufig für simple Berechnungen | Nur wo es sinnvoll ist | Kein nutzloser Overhead |
| Code-Länge | Viel Boilerplate (useMemo, useCallback) | Kein Memoizing-Code nötig | Kürzerer, leserlicherer Code |
| Konsistenz | Abhängig von Entwickler-Disziplin | Immer konsistent | Einheitliche Performance-Basis |
| Voraussetzungen | Funktioniert mit jedem Code | Rules of React müssen eingehalten sein | Migrationspfad nötig für Legacy-Code |
| React.memo | Oft notwendig für stabile Props | Oft überflüssig | Weniger Wrapper-Boilerplate |
| Debugging | stale deps schwer zu finden | ESLint meldet Verstöße früh | Frühere Fehlererkennun |
Die Tabelle zeigt: Der React Compiler gewinnt in fast allen Dimensionen – mit einer wichtigen Ausnahme. Er erfordert regelkonformen Code, was für Legacy-Codebases einen Migrations-Aufwand bedeutet. Wer diesen Aufwand investiert, bekommt eine Codebase, die konsistenter performant ist, weniger Memoizing-Boilerplate enthält und strukturell sauberer ist – unabhängig vom Compiler-Benefit.
Mironsoft
React-Performance, Compiler-Migration und Codebase-Modernisierung
React Compiler für euer Projekt einführen?
Wir analysieren bestehende React-Codebases auf Rules-of-React-Verstöße, beheben sie systematisch und begleiten die schrittweise Aktivierung des React Compilers – von ESLint-Audit bis zur vollständigen Compiler-Integration.
Compiler-Audit
eslint-plugin-react-compiler einrichten und alle Verstöße dokumentieren und priorisieren
Verstöße beheben
Direkte Mutationen, bedingte Hooks und Side-Effects in Render-Funktionen systematisch eliminieren
Aktivierung
Schrittweise Compiler-Aktivierung mit Performance-Messung und Regression-Testing
10. Zusammenfassung
Der React Compiler ist keine magische Performance-Lösung, sondern ein präzises Werkzeug für sauberen React-Code. Er analysiert Komponentencode zur Build-Zeit und fügt automatisch Memoizing ein – präziser und konsistenter als manuelles useMemo und useCallback. Er macht Dependencies-Arrays überflüssig, eliminiert stale-Closure-Bugs durch falsche Deps und reduziert Memoizing-Boilerplate erheblich. React.memo wird an vielen Stellen redundant. Die Voraussetzung ist regelkonformer Code: keine direkten Mutationen, keine bedingten Hook-Aufrufe, keine Side Effects in Render-Funktionen.
Für neue Projekte ist die Einführung des React Compilers von Anfang an einfach: ESLint-Plugin installieren, Compiler aktivieren, sauberen Code schreiben. Für bestehende Codebases ist ein schrittweiser Ansatz der richtige Weg: Erst die Verstöße mit dem ESLint-Plugin finden und beheben, dann den Compiler im Annotation-Only-Modus testen, danach vollständig aktivieren. Die Performance-Gewinne sind real – typischerweise 10–30% weniger Re-Renders in komplexen Komponentenbäumen – aber sie sind ein Nebenprodukt des eigentlichen Ziels: eine Codebase, die die Rules of React konsequent einhält.
React Compiler — Das Wichtigste auf einen Blick
Was der Compiler tut
Build-Zeit-Analyse und automatisches Memoizing – präziser als manuelles useMemo/useCallback. Keine neue Runtime, keine Semantikänderung, nur Optimierung.
Voraussetzungen
Rules of React einhalten: keine State-Mutationen, keine bedingten Hooks, keine Side Effects in Render. eslint-plugin-react-compiler prüft Verstöße.
Aktivierung
babel-plugin-react-compiler für Babel/Vite. Nativ in Next.js 15 experimental.reactCompiler. Schrittweise mit Annotation-Only-Modus möglich.
Auswirkung auf Code
useMemo, useCallback und React.memo werden weitgehend überflüssig. Dependencies-Arrays entfallen. Codebase kürzer, leserlicher und konsistenter performant.