# React: Komponenten als Schnittstellen

URL: https://www.mikebild.dev/de/blog/react-komponenten-als-schnittstellen/

Eine React-Komponente ist kein Template, dem jemand etwas Logik beigemischt hat. Diese Lesart ist verbreitet, weil JSX auf den ersten Blick wie eine Template-Sprache aussieht – und sie erzeugt genau die Komponenten, die ein halbes Jahr später niemand mehr anfassen will: Markup mit eingestreuten Bedingungen, fünfzehn Props ohne erkennbare Ordnung, und irgendwo tief innen ein Ajax-Aufruf. Eine Komponente ist eine Schnittstelle. Props sind ihr öffentlicher Vertrag, State ist ein privates Implementierungsdetail, und das Render-Ergebnis ist die Erfüllung des Vertrags. Wer Props so entwirft, wie man eine gute API entwirft – minimal, in der Sprache der Fachdomäne benannt, ohne durchgereichte Interna –, bekommt Komponenten, die Personalwechsel und Moden überleben.

Seit React 0.14 ist das keine bloße Interpretation mehr, das Framework selbst spricht diese Sprache: Eine Komponente darf eine schlichte Funktion sein, die Props entgegennimmt und Elemente zurückgibt. Kein `createClass`, keine Klasse, kein Lifecycle – eine Funktionssignatur. Deutlicher lässt sich „Schnittstelle" kaum ausdrücken.

## Props sind der Vertrag, State ist ein Detail

In einem Abrechnungsprojekt, an dem ich gerade arbeite, rendern wir eine Liste offener Rechnungen. Die kleinste Einheit ist eine Zeile, und sie ist bewusst eine zustandslose Funktionskomponente:

```jsx
// InvoiceRow.js - a stateless functional component (React 0.14)
const InvoiceRow = ({ invoice, isSelected, onSelect }) => (
  <tr className={isSelected ? 'selected' : ''}
      onClick={() => onSelect(invoice.id)}>
    <td>{invoice.number}</td>
    <td>{invoice.customer}</td>
    <td className="amount">{formatAmount(invoice.total)}</td>
  </tr>
);
```

Aus Sicht des Aufrufers lautet der Vertrag: Gib mir eine Rechnung, sage mir, ob sie ausgewählt ist, und nenne mir einen Callback für die Auswahl. Mehr nicht. Die Komponente weiß nichts über REST-Endpunkte, nichts über einen Store, nichts über die Session des angemeldeten Nutzers. Genau deshalb lässt sie sich in der Detailansicht, im Suchergebnis und im Monatsabschluss gleichermaßen verwenden – und genau deshalb kann ein Test sie ohne Server und ohne Browser-Verrenkungen aufrufen.

Der Gegenspieler des Vertrags ist der State. In den offiziellen Docs steht dazu eine Prüfliste, die ich inzwischen fast wörtlich in Code-Reviews zitiere. Ein Wert gehört nur dann in den State, wenn alle drei Fragen mit Nein beantwortet werden:

- Kommt der Wert über Props herein?
- Bleibt er über die Zeit unverändert?
- Lässt er sich aus anderen Props oder State-Werten ableiten?

Mir ist aufgefallen, dass die meisten React-Probleme, die ich in Reviews sehe, keine Render-Probleme sind, sondern Vertragsprobleme: Props werden beim Mounten in den State kopiert und laufen danach auseinander, abgeleitete Werte werden gespeichert statt berechnet, und plötzlich gibt es zwei Wahrheiten für dieselbe Rechnung. Wer State konsequent als privates Detail behandelt und alles Vertragliche in Props ausdrückt, bekommt diese Klasse von Fehlern gar nicht erst.

## propTypes: der Vertrag steht im Code

Ein Vertrag, der nur im Kopf des Autors existiert, ist keiner. React bringt dafür `propTypes` mit – seit 0.14 direkt im `react`-Paket, egal ob man mit `React.createClass` oder mit ES2015-Klassen arbeitet:

```jsx
InvoiceRow.propTypes = {
  invoice: React.PropTypes.shape({
    id: React.PropTypes.string.isRequired,
    number: React.PropTypes.string.isRequired,
    customer: React.PropTypes.string.isRequired,
    total: React.PropTypes.number.isRequired
  }).isRequired,
  isSelected: React.PropTypes.bool,
  onSelect: React.PropTypes.func.isRequired
};

InvoiceRow.defaultProps = {
  isSelected: false
};
```

Technisch sind das nur Warnungen in der Entwicklungsumgebung – kein Compiler, keine Garantie zur Laufzeit im Produktivbetrieb. Trotzdem halte ich `propTypes` für eines der unterschätztesten Werkzeuge im aktuellen React-Ökosystem, aus einem Grund, der mit Typprüfung wenig zu tun hat: Sie sind Dokumentation, die nicht unbemerkt veralten kann. Wenn eine Kollegin `InvoiceRow` zum ersten Mal öffnet, liest sie den Vertrag, bevor sie eine Zeile Implementierung sieht. Und wenn jemand den Vertrag bricht, meckert die Konsole beim nächsten Entwicklungslauf.

Nebenbei entschärfen `propTypes` auch ein Team-Problem, das gerade viele Codebasen betrifft: das Nebeneinander von `React.createClass` und ES2015-Klassen. Wir schreiben Neues mit Babel und Klassen beziehungsweise als zustandslose Funktionen, aber ältere Teile der Anwendung stecken noch im `createClass`-Stil – und das wird auch eine Weile so bleiben, denn niemand schreibt eine laufende Anwendung nur wegen einer Syntaxfrage um. Solange jede Komponente ihren Vertrag als `propTypes` deklariert, ist das erstaunlich egal. Der Aufrufer sieht in beiden Fällen dieselbe Schnittstelle, und die Migration einer einzelnen Komponente von einem Stil zum anderen bricht nichts, was der Vertrag nicht sofort melden würde.

Interessant wird es, wenn man `propTypes` als Entwurfswerkzeug benutzt statt als Pflichtübung im Nachhinein. In dem Abrechnungsprojekt schreiben wir die `propTypes` inzwischen zuerst, bevor die Komponente Markup bekommt. Das fühlt sich zunächst umständlich an, zwingt aber zu der einen Frage, um die es geht: Was muss der Aufrufer wirklich liefern? Eine Komponente, deren `propTypes`-Block länger ist als ihr Render-Code, ist fast immer ein Hinweis darauf, dass hier zwei oder drei Komponenten in einer stecken.

## Zerlegen wie in „Thinking in React"

Die offiziellen Docs enthalten mit „Thinking in React" einen Text, der genau diese Entwurfshaltung als Methode beschreibt – und der meiner Meinung nach viel zu oft übersprungen wird, weil er nach Einsteigerlektüre klingt. Die Methode ist schlicht: Rechtecke über das UI-Mock zeichnen, jedem Rechteck eine einzige Verantwortung geben, zuerst eine statische Version ohne jeden State bauen, dann den minimalen State identifizieren und ihn im nächstgelegenen gemeinsamen Vorfahren ansiedeln.

Übersetzt in die Schnittstellen-Sicht heißt das: Jedes Rechteck bekommt einen Vertrag, und die statische Version ist der Beweis, dass der Vertrag trägt, bevor irgendetwas Dynamisches passiert. Der Schritt „minimalen State finden" ist dann kein React-Trick, sondern das, was man beim Entwurf eines Datenmodells auch tut – jede Redundanz, die man jetzt zulässt, muss man später von Hand synchron halten.

Das ist keine neue Idee. Wer schon einmal ein Modul-Interface geschnitten oder einen Service-Vertrag verhandelt hat, erkennt das Muster sofort wieder. Neu ist, dass React diese Disziplin bis in die kleinste UI-Einheit hinunter bezahlbar macht: Eine Funktion mit drei Props ist billig genug, um sie wegzuwerfen und neu zu schneiden, wenn der Schnitt nicht stimmt.

## Container und Darstellung trennen

Irgendwo müssen die Daten allerdings herkommen. Dan Abramov hat dafür im vergangenen Jahr die Unterscheidung zwischen Container- und Präsentationskomponenten aufgeschrieben, die sich bei uns im Projekt schnell durchgesetzt hat: Präsentationskomponenten kennen nur ihre Props, Container kennen die Datenquelle und reichen Ergebnisse als Props weiter.

```jsx
// InvoiceListContainer.js - knows where the data comes from
class InvoiceListContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { invoices: [], isLoading: true, selectedId: null };
    this.handleSelect = this.handleSelect.bind(this);
  }

  componentDidMount() {
    fetchInvoices().then(invoices =>
      this.setState({ invoices, isLoading: false })
    );
  }

  handleSelect(invoiceId) {
    this.setState({ selectedId: invoiceId });
  }

  render() {
    const { invoices, isLoading, selectedId } = this.state;
    if (isLoading) return <LoadingHint />;
    return (
      <InvoiceList
        invoices={invoices}
        selectedId={selectedId}
        onSelect={this.handleSelect}
      />
    );
  }
}
```

Der Container ist bewusst langweilig: Er lädt, er hält den Auswahlzustand, er reicht weiter. `InvoiceList` und `InvoiceRow` bleiben reine Funktionen über ihren Props. Der Datenfluss ist damit an jeder Stelle ablesbar – Daten fließen als Props nach unten, Ereignisse laufen als Callbacks nach oben:

```mermaid
flowchart TD
  C[InvoiceListContainer<br/>State: invoices, isLoading, selectedId] -->|Props: invoices, selectedId, onSelect| L[InvoiceList]
  L -->|Props: invoice, isSelected, onSelect| R1[InvoiceRow]
  L -->|Props: invoice, isSelected, onSelect| R2[InvoiceRow]
  R1 -.->|onSelect mit invoice.id| C
```

Wer Redux einsetzt – und der Aufstieg von Redux ist gerade in vollem Gange –, kennt dieses Muster bereits: `connect` erzeugt nichts anderes als Container. Der Punkt ist aber unabhängig von der Bibliothek. Die Trennung lohnt sich auch in einer Anwendung ganz ohne Store, weil sie die teuren Komponenten (die mit Datenzugriff) von den billigen (die mit reiner Darstellung) trennt. Die billigen sind die, die man wiederverwendet, im Styleguide zeigt und in fünf Minuten testet.

## Komposition über children statt Vererbung

Die zweite Stelle, an der das Schnittstellen-Denken konkret wird, ist Wiederverwendung von Rahmen und Layout. Der Reflex aus der objektorientierten Welt wäre Vererbung: eine `BasePanel`-Klasse, von der Spezialfälle erben. In React habe ich das noch in keinem Fall gebraucht. Stattdessen bekommt der Vertrag einen Platzhalter – `children`:

```jsx
// Panel.js - a layout component with a slot for arbitrary content
const Panel = ({ title, children }) => (
  <div className="panel">
    <h2 className="panel-title">{title}</h2>
    <div className="panel-body">{children}</div>
  </div>
);

// usage
<Panel title="Open invoices">
  <InvoiceList invoices={invoices} selectedId={selectedId} onSelect={handleSelect} />
</Panel>
```

`Panel` weiß nichts über Rechnungen, `InvoiceList` weiß nichts über Panels. Beide Verträge bleiben minimal, und jede Kombination ist am Aufrufort sichtbar, statt in einer Vererbungshierarchie versteckt zu sein. Braucht ein Panel mehrere Einschubstellen – etwa eine Werkzeugleiste neben dem Titel –, ist auch das nur eine weitere Prop, die ein Element entgegennimmt. Elemente sind in React gewöhnliche Werte, und Verträge, die Werte austauschen, sind leichter zu verstehen als Verträge, die Ableitung verlangen.

## Die ehrliche Grenze: Props über viele Ebenen

So weit die Stellen, an denen die Schnittstellen-Sicht trägt. Es gibt eine Stelle, an der sie im Moment wehtut, und die will ich nicht verschweigen: Props, die über viele Ebenen durchgereicht werden müssen.

Das klassische Beispiel ist der angemeldete Nutzer. Das Nutzermenü ganz unten im Baum braucht ihn, also wandert er als Prop durch Komponenten, die ihn selbst gar nicht verwenden:

```mermaid
flowchart LR
  App -->|currentUser| Page
  Page -->|currentUser| Toolbar
  Toolbar -->|currentUser| UserMenu
```

`Page` und `Toolbar` haben `currentUser` nicht in ihrem fachlichen Vertrag – sie transportieren ihn nur. Jede Zwischenebene, die eine fremde Prop durchreicht, verwässert genau die Minimalität, für die ich oben argumentiert habe. Und eine wirklich befriedigende Standard-Antwort darauf gibt es Anfang 2016 nicht. Es gibt Linderung:

- den Baum umbauen und Komposition nutzen – `App` rendert das `UserMenu` selbst und reicht das fertige Element nach unten, statt der Daten
- weitere Container tiefer im Baum ansetzen, die sich ihre Daten selbst besorgen, bei Redux etwa per `connect`
- anwendungsweite Daten grundsätzlich in einen Store legen und nur noch lokale Verträge per Props führen

React hat zwar einen Context-Mechanismus, der genau dieses Problem adressieren soll – aber die Docs sagen dazu wörtlich, das Feature sei experimentell und die API voraussichtlich instabil. Auf so etwas baue ich keine Anwendung, die länger leben soll als die Warnung. Im Projekt tolerieren wir Durchreichen über zwei, drei Ebenen, bauen bei mehr den Baum um und setzen für wirklich globale Dinge auf den Store. Das ist ein Kompromiss, kein Zustand, mit dem ich zufrieden bin. Hier ist das Ökosystem schlicht noch nicht fertig.

## Was ich daraus mitnehme

Die Qualität einer Komponente entscheidet sich nicht in ihrem Render-Code, sondern am Aufrufort. Deshalb stelle ich im Review inzwischen zuerst Fragen an die Props-Liste, nicht an die Implementierung: Verstehe ich den Vertrag, ohne die Datei zu öffnen? Sprechen die Namen die Sprache der Fachdomäne – `invoice`, `onSelect` – oder die Sprache der Technik dahinter? Steckt eine Prop darin, die nur existiert, weil ein tieferes Kind sie braucht? Könnte ein zweiter, ganz anderer Aufrufer diese Komponente verwenden, ohne dass sie es merkt?

Templates beantworten keine dieser Fragen, Schnittstellen schon. React gibt einem dafür erstaunlich wenig vor – eine Funktion, Props, `children`, dazu `propTypes` als lesbaren Vertrag – und genau diese Kargheit ist der Wert. Die Bibliothek unter der Komponente wird sich weiterdrehen; allein in den letzten anderthalb Jahren haben sich Build-Werkzeuge, Sprachstandard und State-Bibliotheken einmal komplett gedreht. Ein sauber geschnittener Vertrag aus drei Props hat gute Chancen, das alles zu überstehen. Ein Template mit etwas Logik hat sie nicht.

## Weiterführende Quellen

- Repository mit Beispielcode: [github.com/MikeBild/react-tutorial](https://github.com/MikeBild/react-tutorial)
- Thinking in React (offizielle React-Dokumentation): [react.dev/learn/thinking-in-react](https://react.dev/learn/thinking-in-react)
- React v0.14 – Release-Ankündigung mit stateless functional components: [legacy.reactjs.org/blog/2015/10/07/react-v0.14.html](https://legacy.reactjs.org/blog/2015/10/07/react-v0.14.html)
- Dan Abramov: Presentational and Container Components (2015): [medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0)
