Polymorphe Komponenten mit dem as Prop in React
AI generated
</>
{ }
React · TypeScript · Component Design
Polymorphe Komponenten mit dem as Prop
flexibel und typsicher in React und TypeScript

Polymorphe Komponenten lassen den Aufrufer entscheiden, welches HTML Element tatsaechlich gerendert wird, ohne dass die Komponente selbst dafuer eine eigene Variante braucht. Mit dem as Prop und ElementType aus TypeScript bleibt diese Flexibilitaet vollstaendig typsicher, inklusive korrekt vererbter Props und funktionierendem Ref Forwarding.

18 Min. Lesezeit as Prop · ElementType · ComponentPropsWithoutRef React 19 · TypeScript 5

1. Was polymorphe Komponenten wirklich loesen

Eine polymorphe Komponente rendert je nach uebergebenem Prop ein anderes HTML Element oder eine andere Komponente, ohne dass sich das aeussere Verhalten oder die Props dabei aendern. Der klassische Fall: Ein Button soll manchmal als <button>, manchmal als <a> und manchmal als React Router Link gerendert werden, aber immer identisch aussehen und sich identisch verhalten. Ohne polymorphe Komponenten muesste man drei separate Komponenten pflegen, die sich nur im Root Element unterscheiden.

Design Systeme stossen dieses Problem besonders haeufig, weil Buttons, Text und Layout Container aus semantischen Gruenden unterschiedliche Elemente brauchen. Eine Ueberschrift soll wahlweise als h1 bis h6 gerendert werden, ein Text Baustein manchmal als span, manchmal als p, manchmal als label. Polymorphe Komponenten mit dem as Prop loesen genau dieses Problem: eine einzige Implementierung, ein einziger Satz an visuellen Props, aber freie Wahl des tatsaechlichen DOM Elements durch den Aufrufer.

2. Das as Prop Grundprinzip

Das Grundprinzip einer polymorphen Komponente ist simpel: Ein Prop namens as nimmt einen Elementnamen oder eine Komponente entgegen, und die Implementierung rendert dynamisch genau dieses Element. In purem JavaScript sieht das so aus, dass man den Wert von as in einer Variablen mit Grossbuchstaben speichert, weil JSX Grossbuchstaben als Komponentenreferenz und Kleinbuchstaben als natives HTML Element interpretiert. Diese Variable wird dann anstelle eines festen Tag Namens im JSX verwendet.

Der eigentliche Wert von polymorphen Komponenten zeigt sich erst, wenn zusaetzlich alle Standard Props des jeweiligen Elements korrekt durchgereicht werden. Ein als as="a" gerenderter Button muss href akzeptieren, waehrend derselbe Button als as="button" stattdessen type="submit" akzeptieren sollte. Ohne saubere Typisierung verliert man hier entweder Typsicherheit komplett oder muss jede Kombination manuell absichern, was bei wachsender Anzahl unterstuetzter Elemente schnell unhandlich wird.


// Text.jsx — minimal polymorphic component, plain JavaScript version
function Text({ as: Component = "span", className, children, ...rest }) {
  return (
    <Component className={className} {...rest}>
      {children}
    </Component>
  );
}

// Usage — same visual component, different rendered elements
// <Text>Default span</Text>
// <Text as="p" className="lead">Rendered as a paragraph</Text>
// <Text as="label" htmlFor="email">Rendered as a label</Text>
// <Text as={Link} to="/pricing">Rendered as a router Link</Text>

3. Typisierung mit ElementType

In TypeScript beginnt eine typsichere polymorphe Komponente mit dem eingebauten Typ ElementType. Er akzeptiert sowohl String Literale wie "button" oder "div" als auch React Komponenten Referenzen. Der generische Parameter der Komponente wird an ElementType gebunden, sodass TypeScript bei jedem Aufruf weiss, welches konkrete Element gerade aktiv ist, und die passenden Props dafuer einfordert.

Ohne ElementType muesste man entweder auf string zurueckgreifen, was jede Typsicherheit fuer die Props aufgibt, oder fuer jedes unterstuetzte Element eine eigene Overload Signatur schreiben, was bei polymorphen Komponenten mit vielen moeglichen Elementen schnell unwartbar wird. ElementType gibt genau die richtige Balance: generisch genug fuer beliebige Elemente, aber praezise genug, um bei as="button" automatisch onClick mit dem richtigen Event Typ anzubieten.


// polymorphic-types.ts — the ElementType foundation
import type { ElementType, ComponentPropsWithoutRef } from "react";

// Generic constraint: C must be a valid tag name or component
type PolymorphicProps<C extends ElementType, Props = object> = Props & {
  as?: C;
} & Omit<ComponentPropsWithoutRef<C>, keyof Props | "as">;

// Example: a Box component with its own props plus the target element's props
type BoxOwnProps = {
  padding?: "sm" | "md" | "lg";
};

function Box<C extends ElementType = "div">({
  as,
  padding = "md",
  className,
  ...rest
}: PolymorphicProps<C, BoxOwnProps>) {
  const Component = as || "div";
  return <Component className={`box box-${padding} ${className ?? ""}`} {...rest} />;
}

// TypeScript now knows the exact prop shape per "as" value:
// <Box as="a" href="/docs">   -> href is required/typed for anchors
// <Box as="button" onClick={...} />  -> onClick typed as button event

4. Props Vererbung mit ComponentPropsWithoutRef

Damit eine polymorphe Komponente wirklich als Drop in Replacement fuer das native Element funktioniert, muessen alle Standard Props des Zielelements automatisch verfuegbar sein, ohne sie manuell aufzulisten. ComponentPropsWithoutRef<C> extrahiert genau diese Props aus dem generischen Parameter C, egal ob es sich um ein natives Element oder eine eigene Komponente handelt. Das WithoutRef im Namen ist wichtig, weil ref bei polymorphen Komponenten separat behandelt werden muss.

Der Trick bei der Kombination aus eigenen Props und geerbten Props liegt im Omit: Eigene Props wie padding im vorherigen Beispiel sollen Vorrang vor gleichnamigen nativen Props haben, falls es Ueberschneidungen gibt. Ohne dieses Omit wuerde TypeScript bei Namenskollisionen einen Union Typ bilden, der in der Praxis oft nicht das gewuenschte Verhalten fuer die polymorphe Komponente ergibt. Diese Reihenfolge, eigene Props zuerst definieren und dann native Props mit Ausnahme der eigenen Namen hinzufuegen, ist der zuverlaessigste Ansatz.

5. Ref Forwarding bei polymorphen Komponenten

Ref Forwarding ist der Teil, der bei polymorphen Komponenten am haeufigsten falsch oder unvollstaendig implementiert wird. Da das tatsaechliche Zielelement erst zur Laufzeit feststeht, muss der Ref Typ ebenfalls generisch sein: Ein as="a" Aufruf braucht einen HTMLAnchorElement Ref, ein as="button" Aufruf einen HTMLButtonElement Ref. Der Hilfstyp ElementRef<C> aus React loest genau das, indem er den korrekten Ref Typ aus dem generischen Element Parameter ableitet.

Seit React 19 ist ref ein normales Prop und muss nicht mehr zwingend ueber forwardRef weitergereicht werden, was den Code fuer polymorphe Komponenten spuerbar vereinfacht. In aelteren Codebasen mit React 18 oder frueher bleibt forwardRef notwendig, kombiniert mit einem expliziten generischen Typ Parameter, weil forwardRef selbst generische Komponenten nur eingeschraenkt unterstuetzt und man haeufig eine kleine Typ Assertion braucht, um beide Generics sauber durchzureichen.


// Button.tsx — polymorphic component with correctly typed ref (React 19)
import type { ElementType, ElementRef, ComponentPropsWithoutRef } from "react";

type ButtonOwnProps = {
  variant?: "primary" | "ghost";
};

type ButtonProps<C extends ElementType> = ButtonOwnProps & {
  as?: C;
  ref?: ElementRef<C> | null;
} & Omit<ComponentPropsWithoutRef<C>, keyof ButtonOwnProps | "as" | "ref">;

function Button<C extends ElementType = "button">({
  as,
  variant = "primary",
  ref,
  className,
  ...rest
}: ButtonProps<C>) {
  const Component = as || "button";
  return (
    <Component ref={ref} className={`btn btn-${variant} ${className ?? ""}`} {...rest} />
  );
}

// Usage — ref type adapts automatically to the "as" value
// const anchorRef = useRef<HTMLAnchorElement>(null);
// <Button as="a" href="/docs" ref={anchorRef}>Docs</Button>

6. Eigene Polymorphic Component Factory bauen

Sobald ein Design System mehr als zwei oder drei polymorphe Komponenten braucht, lohnt sich eine gemeinsame Factory Funktion, die den generischen Typ und die Ref Weiterleitung an einer einzigen Stelle definiert. Statt jede Komponente einzeln mit der vollen generischen Signatur zu schreiben, kapselt eine Hilfsfunktion createPolymorphicComponent das Boilerplate und liefert eine fertige, typisierte Komponente zurueck. Das reduziert Wiederholung erheblich und stellt sicher, dass alle polymorphen Bausteine desselben Design Systems sich identisch verhalten.

Eine solche Factory nimmt typischerweise eine Render Funktion entgegen, die das Standardelement plus die eigenen Props kennt, und gibt eine Komponente zurueck, die bereits vollstaendig fuer beliebige as Werte generisch typisiert ist. Dieser Ansatz zahlt sich vor allem in Bibliotheken mit zehn oder mehr polymorphen Komponenten aus, wo die manuelle Wiederholung der generischen Signatur sonst zu einer erheblichen Fehlerquelle bei Refactorings wuerde.

7. Barrierefreiheit bei wechselnden Elementen

Ein oft uebersehener Aspekt polymorpher Komponenten ist, dass ein Wechsel des Root Elements automatisch die Semantik fuer Screenreader und Tastaturnutzer aendert. Ein <button> ist per Definition fokussierbar und per Enter oder Leertaste ausloesbar, waehrend ein <div> das nicht ist. Wird eine Button Komponente mit as="div" gerendert, ohne zusaetzliche ARIA Attribute und Tastatur Handler zu ergaenzen, entsteht ein Element, das visuell wie ein Button aussieht, sich fuer Screenreader Nutzer aber wie ein bedeutungsloser Container verhaelt.

Die zuverlaessigste Absicherung ist, das as Prop bei interaktiven polymorphen Komponenten auf eine sinnvolle Teilmenge einzuschraenken, etwa nur "button", "a" und explizit unterstuetzte Link Komponenten, statt beliebige Elemente zuzulassen. Zusaetzlich sollte die Komponente bei nicht nativ interaktiven Zielelementen automatisch role="button", tabIndex={0} und einen Tastatur Handler fuer Enter und Leertaste ergaenzen, damit die Barrierefreiheit unabhaengig vom gewaehlten Element erhalten bleibt.

8. Fallstricke und Performance

Ein haeufiger Fehler bei polymorphen Komponenten ist, die Zielkomponente bei jedem Render neu zu berechnen, ohne dabei auf Referenzstabilitaet zu achten. Da Component in der JSX Ausdruck als Variable verwendet wird, fuehrt eine instabile Referenz zwischen Renders dazu, dass React das komplette Element inklusive aller Kindkomponenten neu mountet, statt es nur zu aktualisieren. Das zerstoert lokalen State in Kindkomponenten und kann bei Formularen oder Animationen sichtbare Fehler verursachen.

Ein zweiter Fallstrick betrifft komplexe generische Signaturen: Sehr tief verschachtelte polymorphe Komponenten mit mehreren generischen Ebenen koennen die Typinferenz von TypeScript so stark belasten, dass Editor Autovervollstaendigung spuerbar langsamer wird. In der Praxis reicht meist ein einziger generischer Parameter fuer as, zusaetzliche Generics fuer andere Aspekte der Komponente sollten nur eingefuehrt werden, wenn sie wirklich noetig sind, nicht aus reinem Voraus Design.

9. as Prop im Vergleich zu Alternativen

Der as Prop ist nicht die einzige Loesung fuer polymorphe Komponenten. Die folgende Tabelle vergleicht die gaengigen Ansaetze.

Ansatz Typsicherheit Flexibilitaet Komplexitaet
as Prop mit ElementType Hoch, mit Aufwand Beliebiges Element zur Laufzeit Mittel bis hoch
asChild / Slot Pattern Hoch Ein einziges Kindelement ersetzt Root Mittel
Separate Varianten Komponenten Sehr hoch Gering, feste Auswahl Gering pro Komponente, hoch in Summe
any / string Typ fuer as Keine Voll Sehr gering

In der Praxis ist der as Prop mit ElementType der beste Kompromiss fuer generische Design System Bausteine wie Text, Box oder Button, waehrend das Slot Pattern sich besser eignet, wenn genau ein Kindelement das Root ersetzen soll, etwa bei einem Link, der optisch wie ein Button aussehen soll. Separate Varianten Komponenten lohnen sich nur, wenn die Anzahl der moeglichen Elemente klein und stabil ist und maximale Typsicherheit ohne generische Komplexitaet gewuenscht ist.

Mironsoft

React Component Design und TypeScript

Typsichere polymorphe Komponenten fuer euer Design System?

Wir entwerfen und implementieren polymorphe Bausteine mit sauberer TypeScript Typisierung, korrektem Ref Forwarding und barrierefreiem Verhalten unabhaengig vom gewaehlten Element.

Typ Architektur

ElementType und generische Signaturen fuer euer Component API

Component Factory

Wiederverwendbares Boilerplate fuer alle polymorphen Bausteine

Barrierefreiheits-Check

ARIA und Tastatursteuerung unabhaengig vom gerenderten Element

10. Zusammenfassung

Polymorphe Komponenten mit dem as Prop loesen ein zentrales Design System Problem: dieselbe Komponente soll unterschiedliche HTML Elemente rendern koennen, ohne dass Typsicherheit oder Barrierefreiheit verloren gehen. ElementType bindet das gewaehlte Element generisch an die Komponente, ComponentPropsWithoutRef vererbt automatisch alle passenden Standard Props, und ElementRef sorgt fuer einen korrekt typisierten Ref, egal welches Element aktiv ist.

Wer polymorphe Komponenten in einer Bibliothek mit vielen Bausteinen einsetzt, profitiert von einer gemeinsamen Factory Funktion, die das generische Boilerplate an einer Stelle buendelt. Wichtig bleibt in jedem Fall, das as Prop bei interaktiven Komponenten auf sinnvolle Werte einzuschraenken und bei nicht nativ interaktiven Zielelementen ARIA Attribute sowie Tastatur Handler zu ergaenzen, damit die Flexibilitaet nicht auf Kosten der Barrierefreiheit geht.

Polymorphe Komponenten mit dem as Prop — Das Wichtigste auf einen Blick

Grundprinzip

Ein as Prop bestimmt das gerenderte Element, die visuelle Logik bleibt fuer alle Varianten identisch.

Typisierung

ElementType plus ComponentPropsWithoutRef vererbt automatisch die richtigen Props je Element.

Ref Forwarding

ElementRef leitet den korrekten Ref Typ ab. Seit React 19 ist ref ein normales Prop.

Barrierefreiheit

as Werte einschraenken, ARIA und Tastatur Handler bei nicht nativ interaktiven Elementen ergaenzen.

11. FAQ: Polymorphe Komponenten mit dem as Prop

1Was ist eine polymorphe Komponente?
Rendert je nach as Prop ein anderes Element, bei identischer API und identischem visuellem Verhalten.
2Wofuer ElementType?
Erlaubt native Tags und Komponentenreferenzen als as Typ, Grundlage fuer korrekte Props Inferenz.
3Was macht ComponentPropsWithoutRef?
Extrahiert alle Standard Props eines Elements ohne ref, weil ref separat ueber ElementRef typisiert wird.
4Warum ist Ref Forwarding schwieriger?
Der Ref Typ haengt vom Zielelement ab. ElementRef leitet ihn automatisch aus dem generischen Parameter ab.
5Muss ich forwardRef noch nutzen?
Seit React 19 nicht mehr zwingend, in React 18 und frueher bleibt es notwendig.
6Wie bleibt Barrierefreiheit erhalten?
as Werte einschraenken, ARIA und Tastatur Handler bei nicht nativ interaktiven Elementen ergaenzen.
7Unterschied zum Slot Pattern?
Slot ersetzt Root durch ein uebergebenes Kindelement, as Prop waehlt das Element per Wert.
8Warum bricht State beim Re Render?
Instabile Komponentenreferenz laesst React einen neuen Elementtyp erkennen und Kinder neu mounten.
9Lohnt sich eine Factory Funktion?
Ab etwa drei Bausteinen reduziert eine gemeinsame Factory die Wiederholung deutlich.
10Kann ich as einschraenken?
Ja, mit einer Union aus konkreten erlaubten Elementen statt offenem ElementType.