In wiederverwendbaren Komponenten
Ein Ref auf eine selbst geschriebene Komponente funktioniert standardmäßig nicht, weil React Refs nicht automatisch durch Komponentengrenzen weiterreicht. forwardRef und useImperativeHandle lösen das gezielt, ohne die Kapselung der Komponente aufzugeben.
Inhaltsverzeichnis
- 1. Das Grundproblem: Refs stoppen an der Komponentengrenze
- 2. Wie sich das Problem ohne forwardRef zeigt
- 3. Die Lösung: forwardRef macht den Ref explizit steuerbar
- 4. Typische Einsatzfälle in Komponentenbibliotheken
- 5. Wenn direkter DOM-Zugriff zu viel preisgibt
- 6. useImperativeHandle für eine kontrollierte, eigene Ref-API
- 7. Mehrere Refs innerhalb einer Komponente koordinieren
- 8. Wann man forwardRef besser vermeidet
- 9. Zusammenfassender Vergleich der drei Ansätze
- 10. Zusammenfassung
- 11. FAQ
1. Das Grundproblem: Refs stoppen an der Komponentengrenze
Ein ref, der direkt an ein natives DOM-Element wie <input /> übergeben wird, funktioniert immer, weil React Refs bei nativen Host-Elementen automatisch mit der zugrundeliegenden DOM-Node verknüpft. Übergibt man denselben ref jedoch an eine selbst geschriebene Funktionskomponente, etwa eine wiederverwendbare TextField-Komponente, funktioniert das ohne Weiteres nicht: React kennt bei Funktionskomponenten schlicht kein eingebautes Verfahren, den ref automatisch an ein inneres Element weiterzureichen.
Der Grund liegt darin, dass props und ref in React konzeptionell getrennt behandelt werden. Eine Funktionskomponente erhält ref nicht einfach als weiteren Eintrag im props-Objekt, weil das die Bedeutung von Refs verwässern würde, jede Komponente könnte sonst beliebige, undurchsichtige Dinge mit einem als Prop getarnten Ref anstellen. Stattdessen muss eine Komponente explizit signalisieren, dass und wie sie Refs nach innen weiterreicht.
2. Wie sich das Problem ohne forwardRef zeigt
Versucht man, einen ref direkt an eine gewöhnliche Funktionskomponente zu übergeben, gibt React eine Warnung in der Konsole aus, dass Funktionskomponenten standardmäßig keine Refs entgegennehmen können, und der ref bleibt null. Für eine Komponente, die intern etwa einen fokussierbaren <input> rendert, bedeutet das, dass die aufrufende Seite keine Möglichkeit hat, programmatisch den Fokus auf dieses Feld zu setzen, obwohl genau das ein legitimer, häufiger Anwendungsfall ist.
Das Fehlerbild ist besonders in Formular-lastigen Anwendungen relevant, wo etwa nach einer Validierungsfehlermeldung automatisch das erste fehlerhafte Feld fokussiert werden soll. Ohne eine Möglichkeit, den ref gezielt weiterzureichen, müsste diese Logik entweder komplett innerhalb der Wrapper-Komponente selbst gelöst werden, was die Wiederverwendbarkeit einschränkt, oder die Kapselung der Komponente aufgegeben werden.
function TextField(props) {
return <input {...props} />;
}
function Form() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus(); // funktioniert NICHT
}, []);
// Warnung: Function components cannot be given refs
return <TextField ref={inputRef} />;
}
3. Die Lösung: forwardRef macht den Ref explizit steuerbar
forwardRef() umschließt eine Funktionskomponente und gibt ihr einen zweiten Parameter neben props, nämlich ref. Innerhalb der Komponente entscheidet man dann selbst, an welches innere Element dieser ref weitergereicht wird, meist direkt an das native DOM-Element, das die Komponente kapselt. Damit funktioniert die aufrufende Seite exakt so, wie man es von einem nativen Element erwarten würde.
Wichtig ist, dass forwardRef die Kapselung der Komponente nicht aufhebt, sondern bewusst öffnet: Die Komponente entscheidet aktiv, welches interne Element über den Ref erreichbar ist, nicht die aufrufende Seite. Das unterscheidet forwardRef fundamental von einem direkten DOM-Zugriff über document.querySelector, der die Komponentenkapselung komplett umgehen würde.
const TextField = forwardRef(function TextField(props, ref) {
return <input ref={ref} {...props} />;
});
function Form() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current?.focus(); // funktioniert jetzt korrekt
}, []);
return <TextField ref={inputRef} />;
}
4. Typische Einsatzfälle in Komponentenbibliotheken
In wiederverwendbaren UI-Komponentenbibliotheken ist forwardRef nahezu Standard, weil Nutzer solcher Bibliotheken erwarten, dass sich eigene Wrapper-Komponenten wie native Elemente verhalten. Ein Button, ein TextField oder ein Select aus einer Design-System-Bibliothek muss einen ref entgegennehmen können, damit Konsument-Anwendungen etwa Fokus setzen, Messungen mit getBoundingClientRect() vornehmen oder mit Animationsbibliotheken interagieren können, die direkten DOM-Zugriff benötigen.
Auch Formular-Bibliotheken wie React Hook Form setzen intern auf forwardRef-kompatible Eingabekomponenten, weil sie Refs nutzen, um unkontrollierte Eingabefelder direkt zu registrieren und deren Werte ohne zusätzliche Re-Renders auszulesen. Eine eigene Eingabekomponente, die keinen ref weiterreicht, lässt sich mit solchen Bibliotheken nicht ohne Weiteres integrieren.
5. Wenn direkter DOM-Zugriff zu viel preisgibt
Reines Weiterreichen des DOM-Elements per forwardRef gibt der aufrufenden Seite vollen Zugriff auf sämtliche native DOM-API-Methoden, etwa focus(), blur(), remove() oder direkte Style-Manipulation. Für einfache Wrapper ist das meist unproblematisch, bei komplexeren, zustandsbehafteten Komponenten kann das aber zu viel Kontrolle nach außen geben und die interne Kapselung aushebeln.
Ein Beispiel ist eine VideoPlayer-Komponente, die intern mehrere DOM-Elemente und React-State koordiniert. Würde man das rohe <video>-Element direkt per Ref freigeben, könnte die aufrufende Seite Methoden wie load() aufrufen, ohne dass der interne React-State der Komponente davon erfährt, was zu Inkonsistenzen zwischen sichtbarem DOM-Zustand und React-State führen kann.
6. useImperativeHandle für eine kontrollierte, eigene Ref-API
useImperativeHandle löst dieses Problem, indem es innerhalb der Komponente definiert, welches Objekt über den weitergereichten ref tatsächlich sichtbar wird, statt automatisch das rohe DOM-Element freizugeben. Man kombiniert dazu einen internen ref auf das native Element mit einem zweiten, öffentlichen ref, der von außen übergeben wird, und definiert explizit ein Objekt mit genau den Methoden, die die aufrufende Seite nutzen darf.
Im folgenden Beispiel erhält die aufrufende Seite über den Ref ausschließlich play() und pause(), beide implementiert, um zusätzlich den internen React-State der Komponente korrekt zu aktualisieren. Direkter Zugriff auf das rohe <video>-Element oder andere DOM-Methoden bleibt verwehrt, wodurch die Komponente ihre interne Konsistenz garantieren kann, während sie trotzdem eine praktische, imperative API nach außen anbietet.
const VideoPlayer = forwardRef(function VideoPlayer(props, ref) {
const videoRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
useImperativeHandle(ref, () => ({
play() {
videoRef.current.play();
setIsPlaying(true);
},
pause() {
videoRef.current.pause();
setIsPlaying(false);
},
}), []);
return <video ref={videoRef} src={props.src} />;
});
// Nutzung
function App() {
const playerRef = useRef(null);
return (
<>
<VideoPlayer ref={playerRef} src="/demo.mp4" />
<button onClick={() => playerRef.current.play()}>Abspielen</button>
</>
);
}
7. Mehrere Refs innerhalb einer Komponente koordinieren
Komplexere Komponenten müssen oft mehrere interne Refs koordinieren, etwa ein Container-Element und ein fokussierbares Eingabeelement innerhalb einer zusammengesetzten Komponente wie einem Such-Widget mit Dropdown. In solchen Fällen definiert useImperativeHandle eine öffentliche API, die intern auf mehrere unterschiedliche DOM-Refs zugreift, aber nach außen nur eine einzige, konsistente Schnittstelle zeigt.
Diese Kapselung ist ein wichtiger Designvorteil gegenüber dem einfachen Durchreichen eines einzelnen DOM-Elements: Die interne Struktur der Komponente kann sich ändern, etwa wenn ein zusätzliches Wrapper-Element eingeführt wird, ohne dass die öffentliche Ref-API und damit der Vertrag mit konsumierenden Komponenten sich ändert.
8. Wann man forwardRef besser vermeidet
Nicht jede Komponente braucht forwardRef. Für Komponenten, die ausschließlich innerhalb der eigenen Anwendung genutzt werden und niemals einen DOM-Zugriff von außen benötigen, ist forwardRef unnötige Komplexität. Ebenso ist bei reinen Präsentationskomponenten ohne fokussierbares oder messbares inneres Element ein ref häufig gar nicht sinnvoll nutzbar.
Die Faustregel lautet: Sobald eine Komponente Teil einer wiederverwendbaren Bibliothek ist oder mit Bibliotheken interagieren muss, die selbst auf Refs angewiesen sind, etwa Formular- oder Animationsbibliotheken, ist forwardRef fast immer sinnvoll. Für internen, anwendungsspezifischen Code lohnt sich die Investition oft erst, wenn ein konkreter Anwendungsfall für Fokus- oder Mess-Zugriff von außen entsteht.
9. Zusammenfassender Vergleich der drei Ansätze
Zusammengefasst gibt es drei unterschiedliche Ebenen der Ref-Weiterreichung, jede mit einem anderen Grad an Kontrolle und Kapselung. Native Elemente erhalten Refs automatisch, ohne jeden Eingriff. forwardRef reicht den vollständigen DOM-Zugriff kontrolliert an genau ein inneres Element weiter. useImperativeHandle definiert stattdessen eine bewusst eingeschränkte, eigene API, die nur ausgewählte Methoden nach außen zeigt.
Die folgende Tabelle stellt die drei Ansätze mit ihren jeweiligen Eigenschaften gegenüber, um die Entscheidung für den konkreten Anwendungsfall zu erleichtern.
| Ansatz | Kontrollgrad | Typischer Anwendungsfall | Kapselung |
|---|---|---|---|
| Ref auf natives Element | voller DOM-Zugriff automatisch | einfache HTML-Elemente | keine |
| forwardRef ohne useImperativeHandle | voller DOM-Zugriff, gezielt weitergereicht | einfache Wrapper-Komponenten | gering |
| forwardRef mit useImperativeHandle | nur definierte Methoden sichtbar | komplexe, zustandsbehaftete Komponenten | hoch |
| Kein Ref-Support | kein externer DOM-Zugriff | reine, interne Präsentationskomponenten | vollständig |
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
forwardRef: Das Wichtigste auf einen Blick
Kernproblem
Refs werden bei Funktionskomponenten standardmäßig nicht an innere Elemente weitergereicht.
Basis-Lösung
forwardRef reicht den ref-Parameter explizit an ein inneres Element weiter.
Feinsteuerung
useImperativeHandle definiert eine eigene, eingeschränkte Ref-API statt vollem DOM-Zugriff.
Typischer Einsatz
Wiederverwendbare Komponentenbibliotheken und Integration mit Formular-/Animationsbibliotheken.