React: Zehn Jahre "Rethinking Best Practices"

Die JavaScript-Bibliothek für Benutzeroberflächen hat sich stets sanft ohne Breaking Changes entwickelt. Nun steht eine Trendwende zu Fullstack-Frameworks an.

In Pocket speichern vorlesen Druckansicht 10 Kommentare lesen

(Bild: Hung Chung Chih / Shutterstock.com, Skulptur: Auguste Rodin, Der Denker)

Lesezeit: 21 Min.
Von
  • Nils Hartmann
Inhaltsverzeichnis

"Eine JavaScript-Bibliothek, um Benutzeroberflächen zu entwickeln", lautete das schlichte Motto, als React im Mai 2013 das Licht der Open-Source-Welt erblickte. Heute – zehn Jahre später – ist sie aus dem Alltag vieler Entwicklerinnen und Entwickler nicht mehr wegzudenken. React gilt als eine der am weitesten verbreiteten Bibliotheken zum Entwickeln von Single-Page-Anwendungen (SPA). Nicht nur Facebook respektive dessen Mutterkonzern Meta, die React entwickelt und als Open Source veröffentlicht haben, nutzen diese Bibliothek, sondern auch viele bekannte Produkte wie Jira, Figma, Microsoft Outlook und Teams werden damit entwickelt.

React machte viele Dinge anders, als man es damals von etablierten UI-Bibliotheken für das Web und den Desktop kannte – und das gilt bis heute. Für diesen alternativen Ansatz prägte Pete Hunt, React-Entwickler der ersten Stunde, in einem Vortrag auf der JSConf 2013 den noch immer zutreffenden Leitsatz: "Rethinking Best Practices" (siehe Video). So ist denn die Geschichte der Bibliothek von einigen grundlegenden Veränderungen bewährter Praktiken geprägt. Hielten im Zuge von ECMAScript 6 etwa Klassen auch Einzug in React, ließ das Entwicklungsteam wenige Jahre später die Hooks-API folgen, die es möglich machte, React-Komponenten allein mit JavaScript-Funktionen zu entwickeln. In jüngster Vergangenheit deutet sich nun eine Abkehr von Client-seitigen Single Page Applications (SPA) an, hin zu kompletten React-Anwendungen, die zur Laufzeit einen JavaScript-Server benötigen – und daher bevorzugt mit Fullstack-Frameworks erstellt werden sollten.

Empfohlener redaktioneller Inhalt

Mit Ihrer Zustimmmung wird hier ein externes YouTube-Video (Google Ireland Limited) geladen.

Ich bin damit einverstanden, dass mir externe Inhalte angezeigt werden. Damit können personenbezogene Daten an Drittplattformen (Google Ireland Limited) übermittelt werden. Mehr dazu in unserer Datenschutzerklärung.

Historisch betrachtet war das React-Team stets darauf bedacht, technische Brüche (Breaking Changes), wie sie etwa durch das Kombinieren unterschiedlicher Programmier- und Template-Sprachen auftreten, zu vermeiden: Komponenten werden daher ausschließlich mit JavaScript geschrieben. Eine Template-Sprache, wie in vielen UI-Bibliotheken üblich, gibt es nicht. Stattdessen bringt React die JavaScript-Spracherweiterung JSX mit. Diese Erweiterung ermöglicht es, XML-Code direkt in den JavaScript-Code zu schreiben. Ein Compiler sorgt im Build-Prozess dafür, dass daraus gültiger JavaScript-Code entsteht, den Browser verstehen können. Der Gedanke dahinter: Setzen Entwicklerinnen und Entwickler eine JavaScript-Bibliothek zum Bauen von Oberflächen ein, müssen sie die Sprache JavaScript ohnehin kennen oder lernen. Statt eine zweite (Template-)Sprache erlernen zu müssen, können sie auf JavaScript-Erweiterungen zurückgreifen. Nach dem Verständnis des React-Teams ist JSX demnach auch keine Template-Sprache, sondern lediglich "syntaktischer Zucker" für JavaScript. In der aktuellen React-Dokumentation heißt es sogar ganz unbescheiden: "Learning React is learning programming."

Auch populäre Architekturmuster wie das MVC-Pattern (Model View Controller) sucht man in React vergebens. Auf die Trennung in technische Einzelteile ("Separation of Concerns"), also beispielsweise in Model, View und Controller, verzichtet React bewusst und konzentriert alles, was zu einer Komponente gehört, direkt in dieser Komponente. Auf diese Weise soll Code, der fachlich zusammengehört, auch zusammenbleiben und nicht "künstlich" auf mehrere Stellen verteilt werden. Dem liegt die Annahme zugrunde, dass Änderungen oder Erweiterungen von Komponenten ohnehin nicht nur Anpassungen an der UI-, Model- oder Logik-Schicht nach sich ziehen, sondern in der Regel alle Schichten von den Änderungen betroffen sind, sodass lästiges Hin- und Herspringen zwischen den einzelnen Teilen einer Komponente die Folge ist. Außerdem sind die einzelnen technischen Bestandteile einer Komponente in der Regel nicht in anderen Komponenten wiederverwendbar, sodass auch aus diesem Grund eine Aufteilung sinnlos erscheint. Folglich war in React – bis zur Einführung der Hooks-API – die gesamte Komponente die einzige wiederverwendbare Einheit.

Bis heute unverändert geblieben ist die Tatsache, dass sich bei der Entwicklung von React-Anwendungen viel um den optimalen Einsatz des State (Zustands) von Komponenten dreht. Dabei handelt es sich um veränderliche komponenteninterne Daten, etwa den aktuellen Inhalt eines Eingabefeldes. Werden diese Daten geändert, löst React das Neurendern der Komponente aus, deren Zustand verändert wurde – einschließlich aller darunter angesiedelter Komponenten. In React gibt es kein Zwei-Wege-Databinding, also die Möglichkeit, automatisch Änderungen an der Oberfläche (etwa nach der Eingabe in ein Textfeld) in das Model beziehungsweise den Zustand einer Komponente zu übertragen. Die gewünschte Reaktion auf solche Änderungen, die in der Regel durch Ereignisse ausgelöst werden (Textfeld wurde verändert, Checkbox angeklickt, Daten vom Server sind eingetroffen ...), sind in React in der Komponente explizit zu programmieren. Der Lebenszyklus einer Komponente in React ist daher bis heute denkbar einfach: Nach dem initialen Rendern liefert die Komponente das gewünschte UI zurück. An den zurückgelieferten UI-Elementen können Event-Listener gesetzt sein. Tritt ein Event auf, kann die Komponente das Ereignis (Textfeld wurde geändert) verarbeiten und den internen Zustand aktualisieren. Als Folge rendert React die Komponente neu und der Kreislauf beginnt von vorn.

Heise-Konferenz: enterJS 2023

Am 21. und 22. Juni 2023 richten die Veranstalter dpunkt.verlag, heise Developer und iX die Enterprise-JavaScript-Konferenz enterJS in Darmstadt aus. In über 35 Vorträgen kommen JavaScript- und TypeScript-Sprachneuerungen, neue und etablierte Tools und Frameworks – darunter React, SvelteKit und Astro – sowie Barrierefreiheit und Softwarearchitektur zur Sprache.

Ganztägige Workshops stehen sowohl vor Ort als auch online bereit.

Auszug aus dem Programm:

Die Übergabe von Daten zwischen Komponenten erfolgt in React stets von oben nach unten, also von höherliegenden Komponenten zu tieferliegenden. Man spricht daher in React auch vom Unidirectional Dataflow: die Daten fließen immer nur in einer Richtung durch das System. In anderen UI-Architekturen ist es weitverbreitet, dass sich Komponenten auch untereinander kennen – gegebenenfalls separiert etwa durch einen Controller – und einander per Listener über Änderungen informieren oder informieren lassen. Problematisch kann bei dieser Architektur sein, dass nicht unbedingt immer klar ist, wann sich auf welchem Weg welche Daten verändern: Welche Listener reagieren wann auf welche Veränderung? Welche Veränderung löst dann erneut Event-Handler aus? Im schlimmsten Fall kommt es dadurch zu Event-Kaskaden oder "Event-Stürmen", die die Oberfläche unnötig langsam machen.

React wirkt diesem Problem mit dem unidirektionalen Datenfluss entgegen: Es gibt nur genau eine Quelle der Wahrheit und das ist der Zustand einer Komponente. Dieser Ansatz führt allerdings dazu, dass ein Zustand, den viele Komponenten nutzen müssen, in der Komponenten-Hierarchie immer weiter nach oben wandert. In React ist dabei vom Prinzip des "Lifting State up" die Rede.

Während dieses Muster zum Verständnis von Anwendungen sehr hilfreich ist und auch typische Inkonsistenzprobleme vermeiden kann, birgt es auch einen Nachteil: Durch das "Hochschieben" des Zustands in höher angesiedelten Komponenten können schnell sehr komplexe Komponenten entstehen, deren Zustand und Logik überladen sind. Die tiefer angesiedelten Komponenten verkümmern dann zu reinen Darstellungskomponenten. Letztere sind unproblematisch, da sie leicht testbar und möglicherweise auch einfach wiederverwendbar sind, die komplexen "Gott-Komponenten" an der Spitze der Hierarchie hingegen, stellen eine Herausforderung dar. Sie können sich aus mehreren Gründen als Flaschenhals erweisen: Zum einen sind sie schwer testbar, zum anderen schränken sie die Skalierbarkeit des Entwicklungsansatzes ein. Denn im schlimmsten Fall bündeln sich mehrere Fachlichkeiten in einer Komponente, sodass gegebenenfalls auch noch verschiedene Teams daran entwickeln müssen.