React.js im Praxischeck

Keine Kommentare

React.js ist eine JavaScript-Bibliothek, die auf Konferenzen, in den Sozialen Medien und nicht zuletzt bei der codecentric zunehmend ein Thema wird. Ich hatte es immer auf dem Schirm, bisher aber eher als Nischenthema abgetan. Spätestens jetzt ist es also überfällig, mich mit React.js genauer auseinanderzusetzen.

Spannend finde ich vor allem die Frage: Wann macht es für ein Projekt Sinn, React.js einzusetzen? Und daran anschließend:

  • Welches Problem löst React.js für mich?
  • Wie reif ist die Bibliothek?
  • Wie steht es um die Community, die Dokumentation etc.?
  • Ist der resultierende Code wartbar und gut testbar?
  • Wie sieht die Integration in bestehende Applikationen aus und wie gut kann ich 3rd-Party-Artefakte integrieren?
  • Welche Toolunterstützung steht dem Entwickler zur Verfügung?

Am Ende des Artikels hoffe ich, dass Du React.js etwas besser einordnen kannst und beurteilen kannst, unter welchen Umständen React was für Dich ist.

Um das Ganze noch ein wenig anschaulicher zu gestalten, habe ich eine kleine Beispielanwendung gebaut,an der ich einige Konzepte erläutern möchte.
Die Anwendung findet man unter http://soccerreact.herokuapp.com/ und den Sourcecode gibt es unter https://github.com/holgergp/soccerReact
In der Anwendung ist eine Bundesligatabelle nachgebaut, in der man selbst die Vereine hin und herschieben kann. Den ein oder anderen mag das vielleicht an die Beilage aus einem Fußballmagazin erinnern. Das ist reiner Zufall! 😉
screenshotSoccerReact
Als Bonus kann man die Vereinsnamen noch ändern, in dem man einfach auf den Namen klickt. So kann die Tabelle auch nächste Saison noch aktuell gehalten werden 😉
Richtige Persistenz würde den Scope dieses Artikels sprengen. Der Einfachheit halber werden die anfallenden Daten nur clientseitig im Local Storage gehalten.

Also steigen wir doch gleich mal ein:

Wie reif ist React.js?

React.js wurde 2013 von Facebook ins Leben gerufen. Die Bibliothek ist u.a. auch bei Facebook selber im Einsatz und steht unter einer BSD-Lizenz. Somit kann man React.js schon eine gewisse Produktionsreife unterstellen. Obwohl, wie der Schwenk von AngularJS 1.x auf AngularJS 2.x gezeigt hat, das Backing von einem großen Konzern leider nicht viel heißen muss.

Welches Problem löst React.js für mich?

React.js ist, ähnlich wie AngularJS, eine cllientseitige Javascript-Bibliothek.
Der Vergleich hinkt allerdings etwas. Wo AngularJS eine MVC-Struktur abbildet (ob das jetzt gut oder schlecht ist, möchte ich an dieser Stelle nicht diskutieren :)), unterstützt React.js den Entwickler „nur“ beim V (der View) seiner Applikation. Weiter treiben kann man es dann mit der so genannten FLUX-Architektur, in der Komponenten über einen unidirektionalen Kommunikationsfluss über Actions und Stores miteinander kommunizieren.. FLUX werde ich in diesem Artikel allerdings nicht beleuchten, dies würde schlicht zu weit führen.

Was macht React.js jetzt anders in der View-Schicht?
Aus der Sicht der React-Entwickler ist es schlecht, zu viele Stellen in seiner Anwendung zu haben, die den State einer Anwendung verändern können. Two-way-Databinding ist so ein Beispiel. In größeren Anwendungen ist es zuweilen schwierig zu erkennen, wo Datenänderungen ihren Ursprung haben. In einer React.js Applikation wird forciert, dass es nur eine (oder wenige) Stellen gibt, an der State gehalten und verändert werden kann. Der Inhalt dieses States kann dann immutable in der Anwendung weiter verarbeitet werden.
React.js hat den Ruf, schnell zu sein, besonders im Vergleich zu Angular, und auch mit größeren Datenmengen gut klarzukommen. Das liegt daran, dass im Kern alle State-Änderungen über eine Virtual DOM Implementierung vorgehalten werden und somit der Unterschied von zwei State-Änderungen effizient erkannt werden kann und nur das Ergebnis dieses Diffs in den DOM übertragen wird . Als Entwickler bekommt man hiervon allerdings nichts mit.

Des Weiteren besteht eine React-Applikation konsequenterweise aus „Komponenten“, also für sich eigenständige Applikationsteile. Hierfür verwendet React einen hierarchischen Ansatz, wobei der State oft in der Wurzel meines Komponentenbaums verortet ist.
Es bietet sich deshalb an, die Anwendung zuerst auf dem Papier in Bereiche (Komponenten) aufzuteilen und den Datefluss zu definieren bevor mit dem Coding begonnen wird. Wie das geht schauen wir uns später genauer an.

Was wahrscheinlich noch auffällt bei der Verwendung von React.js ist der Einsatz von JSX. JSX ist ein JavaScript-Erweiterung, die Markup direkt als Sprachelement verwendet. Es ist somit recht einfach Komponenten zu definieren. Das Zusammenbauen von Komponenten sieht beispielsweise so aus:

render: function () {
    return (
      <div>
        <SampleComponent myProperty={this.state.myProperty}/>
        <SampleForm onConcertSubmit={this.handleConcertSubmit}/>
      </div>
    );
  }

Man kann zwar auch reines JavaScript einsetzen, allerdings wirkt die Syntax im Vergleich doch eher unhandlich:

React.createElement(
       "div",
       null,
       React.createElement(SampleComponent, { myProperty: this.state.myProperty }),
       React.createElement(SampleForm, { onConcertSubmit: this.handleConcertSubmit })
    );
);

Aber es geht. Der Nachteil ist natürlich, dass man für JSX einen Transpiler braucht.

Wie steht es um die Community und die Dokumentation?

Die Community wächst gefühlt stündlich. Um ein Gefühl zu bekommen ein paar Zahlen zum Vergleich (Stand November 2015):

React.jsAngularJSKnockout.js
Stackoverflow Fragen678313166314840
Github-Sterne30681439676900
Google Hits695.00012.600.000558.000

Aus den Zahlen würde ich zwei Sachen ziehen:

  • Angular ist momentaner Mainstream
  • React hat durchaus schon Relevanz

Die Dokumentation von React.js fand ich im Vergleich zu anderen Bibliotheken und Frameworks recht gut. Ich fühlte mich gut abgeholt, die Dokumentation geht aber auch bei Bedarf in die Tiefe. Hier schon einmal Daumen hoch. Die Qualität der Dokumentation von AngularJS, speziell im Bereich Testing, erreicht React.js aber noch nicht.

Die Beispielanwendung

Dies ist der Moment, an dem wir uns etwas mehr mit der Beispielanwendung befassen: Das Anlegen des Grundgerüsts über einen Yeoman-Generator (https://github.com/newtriks/generator-react-webpack) ging schnell von der Hand.
Als erstes wird die App in Komponenten zerlegt. Heraus kommt eine recht einfache, aber hilfreiche Struktur:
komponentenSoccerReact

Diese Struktur findet sich auch direkt im Code wieder:
Unter src/components liegen die Artefakte:

  • App.js
  • LeagueTable.js
  • Position.js
  • Team.js

die eine direkte Entsprechung in der Grafik haben. Dazu kommt noch run.js,das die Anwendung startet und noch ein Artefakt für Konstanten.

Den State der Anwendung verwalten wir, wie oben skizziert, an einer Stelle. Das ist hier die Ligatabelle (LeagueTable). Die augenscheinlichste Methode einer React-Komponenten ist die render-Funktion. Diese wird immer dann aufgerufen, wenn React eine Änderung feststellt und die Komponente mit einem neuen Zustand rendern möchte.
In der Ligatabelle definieren wir das Grundgerüst der HTML-Struktur und einige Callbacks für die Logik. Zu den Callbacks komme ich allerdings erst später.

Zur Struktur:

var positionNodes = this.state.positions.map(function (posIter) {
      return (
        <Position position={posIter} key={posIter.position}/>
      );
    });
 
return (
 <div className="col-md-6">
   <div className="panel panel-primary">
     <div className="panel-heading">
       <h3 className="panel-title">Ligatabelle</h3>
     </div>
     <div className="panel-body">
       {positionNodes}
     </div>
   </div>
 </div>
);

Sieht erstmal recht vertraut aus. Ungewohnt kann nur sein, dass wir hier Markup und JavaScript vermischen. Aber da habe ich mich schnell dran gewöhnt.
Zwei Dinge sind an dieser Stelle noch spannend: Zum einen bauen wir hier einen – Tag, der die Position-Komponente referenziert (die wir oben im File referenziert haben).
Zum anderen sieht man hier, dass wir (im Gegensatz zu den weiteren Komponenten) auf der .state Eigenschaft der Komponente arbeiten.

Die weiteren Komponenten sind dann im Kern viel simpler. Wenn wir kurz auf die Positionen schauen. Hier sieht (ein Teil) der render-Funktion wie folgt aus:

render: function () {
    const position = this.props.position;
    const team = this.props.position.team;
    return (
      <div>
        <span>
         <Team team={team} positionNumber={position.position} />
      </span>
      </div>
    );
  }

Hier und in den weiteren Kind-Komponenten greifen wir nicht mehr auf den State zu, sondern auf .props.Diese sind immutable. So haben wir nur eine Stelle, an der wir den State halten und ändern. Das Team sieht in der statischen Struktur ähnlich aus.
Schauen wir uns nun doch einmal die Logik an:
Der erste Punkt, den ich umgesetzt habe, war DragAndDrop: Hier macht die Unterscheidung zwischen Team und Position Sinn. Die Positionen bleiben starr, nur die Zuordnung der Teams zu den Positionen ändert sich beim DragAndDrop.
Um DragAndDrop umzusetzen, habe ich react-dnd zur Hilfe genommen. In das Verdrahten von DragAndDrop will ich gar nicht einsteigen. Nur so viel: DropTarget sind die Positionen, DragSource sind die Teams.
Spannend ist hier allerdings: Am Ende eines Drag-Vorgangs rufen wir ein Callback auf, den wir in der LeagueTable definiert und runtergereicht haben. Hier ist dann die zentrale Stelle, an der wir die Tabelle umsortieren.

Der zweite Punkt ist das Editing der Team-Namen: Hierfür habe ich react-wysiwyg verwendet. Das Pattern sieht hier ähnlich aus. In der Team-Komponente definieren wir das Markup:

<ContentEditable
  tagName='div'
  onChange={onChange}
  className='textPointer'
  html={team.name}
  autofocus={true}
  maxLength={200}
  editing={this.props.team.editing}
  preventStyling
  noLinebreaks
 />

Der Callback ist wiederum in der Ligatabelle definiert.

Der dritte erwähnenswerte Punkt ist LocalStorage. Hierfür ist wiederum die Ligatabelle verantwortlich: Hierfür verwenden wir weitere Funktionen einer React-Komponente: getInitialState und componentDidUpdate.

getInitialState definiert den Anfangszustand einer Komponente. Hier überprüfe ich, ob LocalStorage vorhanden ist und ob passende Daten im LocalStorage hinterlegt sind. Abhängig davon werden entweder Default-Werte oder die gespeicherten Werte geladen.
componentDidUpdate wird aufgerufen, wenn sich die Komponenten geändert haben. Hier lege ich den aktuellen Zustand im LocalStorage ab.

Um die Entwicklung des Sourcecodes besser nachvollziehen zu können, habe ich für jeden Entwicklungsschritt einen Branch angelegt: Angefangen von einer Variante ohne Dynamik, bis zu vollständigen Version mit DragAndDrop.

Ist der resultierende Code gut wartbar und testbar?

Ich habe für das Testing Jasmine verwendet und exemplarisch einige Unittests für die LeagueTable-Komponente geschrieben. Hier sind mir in der Tat gar nicht so viele Besonderheiten aufgefallen. Das Test-Arrangement ist etwas ungewöhnlich, das virtuelle Rendering mag etwas verwirren. Dies wird aber über React-Utilities gut unterstützt. Kann man React-Anwendungen also gut unittesten? Ja!

Dadurch dass React den Entwickler dazu ermutigt, die Anwendung in Komponenten zu zerschneiden und der State auch nur an wenigen Stellen gehalten wird, ist die Applikation schon von Grund auf recht gut wartbar. Was vielleicht etwas kritischer sein kann, ist dass die Zustandshaltende Komponente (hier die Ligatabelle) doch recht überfrachtet wirken kann. Mit der FLUX Architektur kann aber auch dieses Problem sehr elegant adressiert werden.

Was ich während der Entwicklung schön fand, waren die sehr gut unterstützenden Fehlermeldungen und Logs. Das kennt man schlechter! JSX kann vielleicht etwas ungewohnt wirken (“Warum noch eine Sprachvariante?”), ich sehe den Einsatz momentan aber als klares Plus.

Es hat zwar ein wenig gebraucht, um in die React-Denke reinzukommen, aber die Entscheidungen sind nachvollziehbar und das Ergebnis finde ich sehr schön.

Integration

Wie am Beispiel gezeigt, ist es nicht sonderlich aufwändig über Third-Party-Bibliotheken komplexere Funktionalität nachzurüsten, um auch anspruchsvollere Anforderungen umzusetzen. Die Integration in bestehende Anwendungen ist natürlich auch möglich. Als ein Beispiel sei hier ngReact genannt. Allerdings muss man dieses natürlich immer im Einzelfall beleuchten.

Tooling

Die verwendete Toolchain ist im Vergleich zu anderen JavaScript-Stacks ähnlich. Allerdings mag sich in einigen Konstellation das Verwenden eines Transpilers wie Babel noch ungewohnt anfühlen. Als Fallback böte sich dann JavaScript (ES5) an.
Ich habe die Anwendung mit IntelliJ entwickelt und dort wurde ich ordentlich unterstützt, kann ich also empfehlen

Wann macht React.js Sinn?

React.js macht vielleicht nicht in jeder Situation Sinn, in der clientseitige Logik oder auch SPAs eingesetzt werden soll. Dafür ist das Investment in die Toolchain und die Art der Denke vielleicht noch zu weit weg vom Mainstream.
In mindestens zwei Szenarien ist es aber meiner Ansicht nach sehr sinnvoll. Zum einen, wenn man ein in sich abgeschlossenes Widget baut (sowie vielleicht die Ligatabelle) oder aber wenn in der Applikation viele Daten verarbeitet und angezeigt werden sollen und Performance ein Thema ist. Desweiteren ist in diesem Kontext ein Blick auf FLUX lohnenswert, um auch größere Anwendungen mit React zu bauen.

Viel Spaß beim Ausprobieren von React.js!

Holger Grosse-Plankermann

Holger Grosse-Plankermann ist seit 2015 für die codecentric als Senior Consultant und Senior Developer tätig. Gerne entwickelt und entwirft er gut testbare Software und bewegt sich dabei sowohl im Backend als auch im Frontend. Holger nutzt dabei oft den Java EE/Spring Stack und ist zudem ein Verfechter von JavaScript- und Web-Technologien.
Er hält immer Ausschau nach innovativen Lösungen für Kundenprobleme. Sein aktuelles Interesse gilt dem Bereich der funktionalen Programmierung, und er bleibt im Bereich JavaScript-Frameworks immer am Ball.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.