Heute noch relevant?
Bevor es Hooks gab, waren Render Props und Children-as-Function die Antwort auf Logik-Wiederverwendung zwischen Komponenten. Ein ehrlicher Blick darauf, wo sie im Hooks-Zeitalter noch echten Mehrwert bieten und wo Custom Hooks klar überlegen sind.
Inhaltsverzeichnis
- 1. Was Render Props ursprünglich lösen sollten
- 2. Children-as-Function als Variante
- 3. Was Hooks an der Ausgangslage verändert haben
- 4. Das Wrapper-Hell-Problem bei verschachtelten Render Props
- 5. Wo Render Props noch echten Sinn ergeben
- 6. Praxisbeispiel: Headless-Komponenten mit Render Props
- 7. Hybride APIs: Custom Hook und Render Prop parallel anbieten
- 8. Wann Custom Hooks klar die bessere Wahl sind
- 9. Entscheidungshilfe für die eigene Codebasis
- 10. Zusammenfassung
- 11. FAQ
1. Was Render Props ursprünglich lösen sollten
Vor der Einführung von Hooks in React 16.8 gab es kaum Möglichkeiten, zustandsbehaftete Logik zwischen Komponenten zu teilen, ohne entweder Vererbung oder Higher-Order-Components zu nutzen. Das Render-Props-Pattern löste dieses Problem, indem eine Komponente statt statischer Kindelemente eine Funktion als Prop entgegennahm, die von der Komponente selbst mit internem State oder berechneten Werten aufgerufen wurde. Die aufrufende Seite entschied dann frei, wie diese Werte gerendert werden.
Eine typische MouseTracker-Komponente etwa verfolgte intern die Mausposition und rief eine render-Prop mit den aktuellen Koordinaten auf. Jede Komponente, die diese Logik nutzen wollte, musste sie nicht selbst implementieren, sondern konnte sich einfach als Consumer an den Render-Prop-Vertrag halten. Das war zu seiner Zeit ein echter Fortschritt gegenüber Vererbungsketten und unübersichtlichen HOC-Verschachtelungen.
class MouseTracker extends React.Component {
state = { x: 0, y: 0 };
handleMouseMove = (event) => {
this.setState({ x: event.clientX, y: event.clientY });
};
render() {
return (
<div onMouseMove={this.handleMouseMove}>
{this.props.render(this.state)}
</div>
);
}
}
// Nutzung
<MouseTracker render={({ x, y }) => (
<p>Maus bei ({x}, {y})</p>
)} />
2. Children-as-Function als Variante
Eine eng verwandte Variante nutzt children selbst als Funktion statt eines separaten render-Props. Statt <MouseTracker render={fn} /> schreibt man <MouseTracker>{fn}</MouseTracker>, wobei React children einfach als beliebigen Wert behandelt, auch als Funktion. Der praktische Unterschied ist gering, aber die Lesbarkeit im JSX wirkt für viele natürlicher, weil sich die Struktur stärker an gewöhnlicher Komponentenverschachtelung orientiert.
Beide Varianten teilen dieselbe Grundidee: Eine Komponente kapselt Logik und Zustand, überlässt aber die Darstellung vollständig der aufrufenden Seite. Das Pattern wird deshalb oft als 'Inversion of Control' beschrieben, weil die Kontrolle darüber, was gerendert wird, von der Logik-Komponente an den Aufrufer übergeben wird.
function MouseTracker({ children }) {
const [position, setPosition] = useState({ x: 0, y: 0 });
return (
<div onMouseMove={(e) => setPosition({ x: e.clientX, y: e.clientY })}>
{children(position)}
</div>
);
}
// Nutzung
<MouseTracker>
{({ x, y }) => <p>Maus bei ({x}, {y})</p>}
</MouseTracker>
3. Was Hooks an der Ausgangslage verändert haben
Mit Custom Hooks lässt sich dieselbe Logik-Wiederverwendung erreichen, ohne dafür eine zusätzliche Wrapper-Komponente im Baum einzuführen. Ein useMousePosition-Hook kapselt exakt dieselbe Zustandslogik wie die MouseTracker-Komponente, gibt die Werte aber direkt als Rückgabewert zurück, ohne dass eine Komponente ihre eigene Render-Logik in eine fremde Funktion auslagern muss.
Der entscheidende Unterschied ist strukturell: Render Props erzeugen zwangsläufig eine zusätzliche Komponentenebene im Baum, mit eigenem Lifecycle und eigenem Platz in den React DevTools. Ein Custom Hook dagegen fügt sich nahtlos in die aufrufende Komponente ein, es entsteht keine zusätzliche Verschachtelung, und der Code liest sich linear statt funktional verschachtelt.
function useMousePosition() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMove = (e) => setPosition({ x: e.clientX, y: e.clientY });
window.addEventListener('mousemove', handleMove);
return () => window.removeEventListener('mousemove', handleMove);
}, []);
return position;
}
// Nutzung -- keine Wrapper-Komponente noetig
function Cursor() {
const { x, y } = useMousePosition();
return <p>Maus bei ({x}, {y})</p>;
}
4. Das Wrapper-Hell-Problem bei verschachtelten Render Props
Sobald mehrere Render-Props-Komponenten kombiniert werden müssen, etwa Mausposition, Fenstergröße und Authentifizierungsstatus gleichzeitig, entsteht schnell tief verschachteltes JSX, umgangssprachlich als 'Wrapper Hell' oder 'Callback Hell' bezeichnet. Jede zusätzliche Logik-Quelle fügt eine weitere Verschachtelungsebene hinzu, und der eigentliche Rendering-Code rutscht immer weiter nach rechts.
Mit Custom Hooks entfällt dieses Problem vollständig, weil mehrere Hooks einfach nacheinander in derselben Komponente aufgerufen werden, ohne dass sich daraus zusätzliche Verschachtelung ergibt. Das ist einer der stärksten praktischen Gründe, warum sich Custom Hooks für Logik-Wiederverwendung im eigenen Anwendungscode durchgesetzt haben.
// Render-Props-Variante: Wrapper Hell
<MouseTracker>
{(mouse) => (
<WindowSize>
{(size) => (
<Auth>
{(user) => (
<Dashboard mouse={mouse} size={size} user={user} />
)}
</Auth>
)}
</WindowSize>
)}
</MouseTracker>
// Hooks-Variante: flach, linear
function Dashboard() {
const mouse = useMousePosition();
const size = useWindowSize();
const user = useAuth();
return <DashboardView mouse={mouse} size={size} user={user} />;
}
5. Wo Render Props noch echten Sinn ergeben
Ein Bereich, in dem das Pattern nicht obsolet geworden ist, sind Bibliotheks-APIs, bei denen die Darstellung eines UI-Elements bewusst der konsumierenden Anwendung überlassen werden soll, während die Bibliothek selbst nur Verhalten und Zustand steuert. Bibliotheken für Datenlisten mit Drag-and-Drop, virtualisierte Listen oder Formular-Felder nutzen Render Props häufig genau deshalb, weil ein Custom Hook allein nicht ausreicht, um zusätzlich auch die DOM-Struktur oder Event-Handler-Bindung an einer bestimmten Stelle im Baum zu steuern.
Der Grund liegt darin, dass ein Hook nur Werte zurückgeben kann, aber keine Kontrolle über die JSX-Struktur an sich hat. Eine Render-Prop-Komponente kann dagegen zusätzlich steuern, wo genau im Baum eine Interaktion registriert wird, etwa onMouseDown auf einem bestimmten Wrapper-Element, was bei reinen Hooks nicht möglich ist, ohne dass der Aufrufer das selbst manuell verdrahtet.
6. Praxisbeispiel: Headless-Komponenten mit Render Props
Ein konkretes Beispiel ist eine Komponente für sortierbare Tabellen, bei der die Bibliothek Sortierlogik und Zustand verwaltet, aber Header-Zellen und Zeilen-Rendering vollständig der Anwendung überlässt. Der Render-Prop-Vertrag stellt dabei sicher, dass Klick-Handler und Sortierstatus korrekt an die richtigen DOM-Elemente gebunden werden, während die visuelle Darstellung frei bleibt.
Solche 'Headless Components' sind ein legitimer und in der Praxis weit verbreiteter Anwendungsfall für Render Props, weil die Trennung zwischen Verhalten und Darstellung genau der Kernidee des Patterns entspricht. Bibliotheken wie Downshift für Autocomplete-Verhalten nutzten historisch genau dieses Muster, wenngleich viele davon mittlerweile parallel auch Hook-basierte APIs anbieten.
function SortableTable({ data, renderHeader, renderRow }) {
const [sortKey, setSortKey] = useState(null);
const sorted = useMemo(() => sortData(data, sortKey), [data, sortKey]);
return (
<table>
<thead>{renderHeader({ sortKey, onSort: setSortKey })}</thead>
<tbody>{sorted.map((row) => renderRow(row))}</tbody>
</table>
);
}
7. Hybride APIs: Custom Hook und Render Prop parallel anbieten
Viele etablierte Bibliotheken lösen die Abwägung nicht endgültig zugunsten eines der beiden Pattern, sondern bieten beide Zugänge parallel an. Intern kapselt ein Custom Hook die eigentliche Zustandslogik, und eine dünne Render-Props- oder Children-Function-Komponente wird lediglich als Wrapper um genau diesen Hook herum gebaut. So profitieren Nutzer, die reinen Wertezugriff brauchen, vom schlanken Hook, während Nutzer, die zusätzlich DOM-Kontrolle benötigen, die Wrapper-Komponente einsetzen können, ohne dass die Bibliothek die Logik zweimal pflegen muss.
Dieses Muster ist besonders in Migrationsphasen sinnvoll, wenn eine Bibliothek historisch mit Render Props aufgebaut wurde und bestehende Konsumenten nicht sofort umgestellt werden sollen. Der Hook wird zur eigentlichen Quelle der Wahrheit, die Render-Props-Komponente bleibt als dünne, rückwärtskompatible Hülle bestehen, was Wartungsaufwand spart und beiden Zielgruppen gerecht wird.
// Hook ist die eigentliche Logik-Quelle
function useToggle(initial = false) {
const [value, setValue] = useState(initial);
const toggle = useCallback(() => setValue((v) => !v), []);
return [value, toggle];
}
// Render-Props-Komponente als duenner Wrapper um den Hook
function Toggle({ children, initial }) {
const [value, toggle] = useToggle(initial);
return children({ value, toggle });
}
8. Wann Custom Hooks klar die bessere Wahl sind
Für den überwiegenden Teil der Logik-Wiederverwendung im eigenen Anwendungscode, etwa Datenfetching, Formular-State, Media-Queries oder Debouncing, ist ein Custom Hook nahezu immer die bessere Wahl. Er ist einfacher zu testen, weil er ohne Rendering-Kontext isoliert per renderHook geprüft werden kann, erzeugt keinen zusätzlichen Komponentenbaum und lässt sich problemlos mit anderen Hooks kombinieren.
Auch aus TypeScript-Sicht sind Hooks angenehmer: Der Rückgabetyp eines Hooks lässt sich präzise typisieren, während bei Render Props zusätzlich die Signatur der Callback-Funktion typisiert werden muss, was in der Praxis zu mehr Typdefinitionen für denselben Nutzen führt. Sobald die Frage lautet, ob die konsumierende Seite auch Kontrolle über die konkrete DOM-Struktur an mehreren Stellen braucht, kippt die Abwägung wieder Richtung Render Props oder Headless-Component-Pattern.
9. Entscheidungshilfe für die eigene Codebasis
Die pragmatische Faustregel lautet: Wenn ausschließlich Werte oder Zustand geteilt werden sollen, ist ein Custom Hook die richtige Wahl. Wenn zusätzlich Kontrolle über bestimmte DOM-Bindungen, Event-Handler-Platzierung oder eine öffentliche, framework-unabhängige Bibliotheks-API benötigt wird, bleibt das Render-Props- oder Children-Function-Pattern eine valide, bewusste Designentscheidung.
Wichtig ist, das Pattern nicht aus Gewohnheit im eigenen Anwendungscode fortzuführen, nur weil es in einer älteren Codebasis so etabliert war. Ein Refactoring zu Custom Hooks lohnt sich in den meisten Fällen, sobald die Render-Prop-Komponente ausschließlich internen Anwendungscode bedient und keine öffentliche, konsumierende API darstellt.
| Kriterium | Render Props / Children-Function | Custom Hooks |
|---|---|---|
| Zusätzliche Komponentenebene im Baum | ja, immer | nein |
| Kombinierbarkeit mehrerer Logik-Quellen | verschachtelt (Wrapper Hell) | flach, linear |
| Kontrolle über DOM-Struktur/Event-Bindung | ja, an der Aufruferseite steuerbar | nur eingeschränkt möglich |
| Isoliertes Testen ohne Rendering | schwieriger | einfach per renderHook |
| Typische Anwendung heute | Headless-Component-Bibliotheken | Anwendungsinterner Logik-Reuse |
Mironsoft
React-Architektur, Performance und Magento-Frontend-Integration
React-Frontends, die schnell bleiben statt mit jedem Feature langsamer zu werden?
Wir prüfen bestehende React-Anwendungen auf unnötige Re-Renders, aufgeblähte Bundles und fragile State-Verwaltung und bauen daraus ein Frontend, das performant bleibt und sich sauber an Magento oder andere Backends anbindet.
Performance-Audit
Re-Renders, Bundle-Größe und Ladezeiten systematisch messen und beheben.
State-Architektur
Context, Zustand und Server State sauber trennen statt alles in einen Topf zu werfen.
Magento-Integration
GraphQL- oder REST-Anbindung an Magento robust und typsicher aufbauen.
10. Zusammenfassung
Render Props: Das Wichtigste auf einen Blick
Historischer Zweck
Render Props lösten Logik-Wiederverwendung vor der Einführung von Hooks.
Größte Schwäche
Verschachtelung mehrerer Render-Props-Komponenten führt zu Wrapper Hell.
Moderne Alternative
Custom Hooks für reinen State- und Logik-Reuse ohne DOM-Kontrolle.
Verbleibender Einsatzbereich
Headless-Component-Bibliotheken mit Bedarf an DOM-Struktur-Kontrolle.