Aus Linux-Magazin 12/2014

Webinterfaces mit React

© William Perugini, 123RF

Vom Unternehmen Facebook kommt das quelloffene Javascript-Framework React, das Weboberflächen geschickt mit Datenschätzen verknüpft. Insbesondere die Renderfunktion macht sich dabei nützlich.

Mit dem Javascript-Framework React [1] erlebt die CGI-Programmierung eine Renaissance. Daneben bietet React aber die Vorzüge reaktiver Programmierung [2], der auch die Javascript-Frameworks Angular [3] und Meteor [4] huldigen. Schreiben Entwickler React-Anwendungen zudem nach dem Entwurfsmuster Flux [5], erhalten sie eine robuste Architektur für den Praxiseinsatz dazu. Der Artikel zeigt eine Beispiel-App [6], die neben React und Flux HTML 5, Javascript, das Geolocation-API [7] sowie CSS 3 verwendet. Ein Report listet dabei die Erdbeben der letzten Tage tabellarisch auf.

CGI und Ajax

Ende der 90er Jahre erzeugten auf Webservern laufende CGI-Skripte die meisten dynamischen Webseiten und sandten diese an den Browser. Perl-Subroutinen generierten Fragmente, welche die Skripte zu einem HTML-Dokument zusammenfügten. Die Anwendungsdaten übergaben diese Skripte in Form von Aufrufparametern.

Dank der XML-HTTP-Request-Objekte (Ajax) ließen sich dynamische Seiten ab Mitte der 2000er Jahre auch im Browser generieren. Ein Javascript-Programm lud Anwendungsdaten per Ajax nach und ersetzte so die CGI-Skripte. Seitdem haben Entwickler zahllose Javascript-Bibliotheken veröffentlicht, die das Programmieren von Javascript-Anwendungen vereinfachen, ein Beispiel ist Jquery.

Seit Beginn der 2010er Jahre gesellen sich zu den Bibliotheken reaktive Javascript-Frameworks. Sie verringern den Programmieraufwand im Vergleich zu Jquery nochmals, denn sie aktualisieren einen DOM-Baum im Browser automatisch oder auf Befehl.

React

Neben Googles Angular und dem Echtzeit-fähigen Meteor gehört React zu den reaktiven Emporkömmlingen. Facebook entwickelt und verwendet es selbst und hat es unter der freien Apache-2.0-Lizenz veröffentlicht. Es läuft auf den aktuellen Versionen der gängigen Browser, derzeit liegt React in der Version 0.11 vor. Ähnlich wie die Subroutinen von Perl erzeugen unter React die so genannten Komponenten den HTML-Code. Ihr Mehrwert: Der Entwickler kann sie wie Funktionen aufrufen und in seiner Anwendung mehrfach einsetzen.

Listing 1 zeigt die React-Komponente »ReportTable« . Das Listing stellt – genau wie Listing 2, Listing 5 und Listing 6 – einen Ausschnitt der Datei »report.js« aus der Beispielanwendung [6] dar, der Artikel erklärt den Code vollständig. Indem es die Methode »createClass()« in der Zeile 2 aufruft, erzeugt das Skript die Komponente »ReportTable« und übergibt zugleich ein Objekt mit der Eigenschaft »render« (Zeile 3), die auf die so genannte Renderfunktion der Komponente verweist. Letztere erzeugt zum Schluss das HTML und verwendet dafür das React-eigene XML-Vokabular JSX [8].

Listing 1

report.js (Auszug)

01 /** @jsx React.DOM */
02 var ReportTable = React.createClass({
03     render: function() {
04         var rows = [];
05         geoSort(this.props.position, this.props.items).forEach(function(item) {
06             item.distance = geoDistance(this.props.position, item);
07             rows.push(<ReportRow item={item} key={item.date_time + " " + item.location}/>);
08         }.bind(this));
09         return (
10         <table>
11             <thead>
12             <tr>
13             <th>Datum</th>
14             <th>Ort</th>
15                  <th>Entf. (km)</th>
16                  <th>Ereig. (h)</th>
17                  <th>Magn.</th>
18                  <th>Tiefe (km)</th>
19             </tr>
20             </thead>
21             <tbody>{rows}</tbody>
22             </table>
23         );
24     }
25 });

XML-Helfer

Dank JSX lässt sich das HTML wie in Listing 1 literal schreiben (Zeilen 10 bis 22), zudem kann der Entwickler wie in Zeile 7 die React-Komponente »ReportRow« aufrufen. In derselben Zeile übergibt das Programm den Rückgabewert von »ReportRow« an die Liste »rows« , die das Resultat in Zeile 21 mit »{rows}« ins erzeugte HTML-Fragment schreibt.

Mit dem reinen JSX-Code würde der Code in Listing 1 noch nicht laufen. Die Zeile 1 veranlasst daher einen Übersetzer, den JSX-Code in natives Javascript zu überführen. Der Prozess ersetzt den Ausdruck »{rows}« in Zeile 21 mit den Werten der Liste »rows« .

Die Renderfunktion aus Listing 1 liest ihre Aufrufparameter über die Eigenschaft »this.props« aus, die Elemente über »this.props.items« (Zeile 5) und die Position über »this.props.position« (Zeile 6). JSX initialisiert die Aufrufparameter in Zeile 7. Die Renderfunktion von »ReportRow« greift auf »this.props.item« und »this.props.key« zu.

Data Binding

Anders als Angular verfügt React von Haus aus nur über ein One-Way-Data-Binding (Abbildung 1). Das heißt, eine Änderung der Anwendungsdaten (Model) zieht die Aktualisierung der Browseransicht (View) nach sich. Den umgekehrten Weg – wie beim Two-Way-Data-Binding – sieht React nicht vor.

Abbildung 1: One-Way- und Two-Way-Data-Binding im Vergleich.

Abbildung 1: One-Way- und Two-Way-Data-Binding im Vergleich.

Im Gegensatz zu Angular aktualisiert React auch die View nicht automatisch. Der Entwickler muss wie in den Zeilen 8 bis 10 von Listing 2 die Methode »setState()« aufrufen, die zweierlei erledigt: Sie verändert die Werte der Statusvariablen in der React-Anwendung und erzwingt im Anschluss eine Neuberechnung durch die Renderfunktion. In den Statusvariablen speichert React die Randbedingungen einer Anwendung. Der Entwickler variiert sie, um letztlich eine dynamische React-Anwendung zu erzeugen.

Listing 2

report.js (Auszug)

01 var Report = React.createClass({
02     getInitialState: function() {
03         return {
04             position: {latitude:64.9, longitude:-21.56} // Reykjavík
05         };
06     },
07     handleUserInput: function(lat, lnt) {
08         this.setState({
09             position: {latitude:lat, longitude: lnt}
10         });
11     },
12     render: function() {
13         return (
14             <div id="container">
15                 <h1>Erdbeben-Report</h1>
16                 <ReportOptions
17                     position={this.state.position}
18                     onUserInput={this.handleUserInput}
19                 />
20                 <ReportTable
21                     position={this.state.position}
22                     items={this.props.items}
23                 />
24                 </div>
25             );
26         }
27 });

Der Aufruf von »setState()« speichert als Objekt einen neuen Standort mit Breiten- und Längengrad als Eigenschaften und der Position als Schlüssel. Dann berechnet React die Renderfunktion (Zeile 12) sowie die Kind-Komponenten »ReportOptions« (Zeile 16) und »ReportTable« (Zeile 20) neu. React ändert den DOM-Baum der Browseransicht aber nur, falls sich das errechnete Markup ändert.

Um ein Two-Way-Data-Binding auch in React-Anwendungen umzusetzen, berechnet der Entwickler wie in der folgenden Beispielanwendung das Modell mittels einer Rückruffunktion neu, sobald ein Nutzer das Formularfeld manipuliert.

Beispielanwendung

Abbildung 2 zeigt die Beispielanwendung [6] in der Browseransicht von Firefox unter Ubuntu 12.04. Der Report fragt die Erdbeben der letzten Tage im Internet ab. Sortiert nach deren Entfernung auf der Erdoberfläche vom Standort stellt er sie tabellarisch und aufsteigend dar. Über die beiden Textfelder am oberen Ende der Seite wählt der Nutzer den Standort. Die Anwendung interpretiert Längen- und Breitengrad im Gradmaß.

Abbildung 2: Der Report zeigt die Erdbeben der letzten Tage für den Standort Reykjavík.

Abbildung 2: Der Report zeigt die Erdbeben der letzten Tage für den Standort Reykjavík.

Ein Klick auf den Knopf mit der Aufschrift »Aktueller Standort« ermittelt den Standort per Geolocation-API – vorausgesetzt der Benutzer stimmt dem zu. Dann trägt er diesen in die vorgesehenen Textfelder ein. Ändert sich ein Wert, stellt die Anwendung den Report neu zusammen, den ein Webserver ausliefert, der zudem in der Lage ist, CGIs auszuführen.

Abbildung 3 zeigt, ebenfalls in der Browseransicht von Firefox, die Erdbebendaten im Json-Format. Aufgrund der Cross Origin Resource Sharing Policy [9] des Anbieters Earthquake Report [10] lädt der Browser die Daten nicht direkt, sondern schaltet gemäß Abbildung 4 das Python-Skript aus Listing 3 dazwischen. Wird der Report angefordert (mit der Ziffer 1 in Abbildung 4 markiert) startet der Webserver das Python-Skript über seine CGI-Schnittstelle. Dafür sorgt die Zeile 1 von Listing 3, die den Python-Interpreter als ausführende Instanz etabliert.

Listing 3

get_data.py

01 #!/usr/bin/env python
02 import urllib2
03
04 print 'Content-Type: application/json\r\n\r\n',
05 print urllib2.urlopen("http://earthquake-report.com/feeds/recent-eq?json").read(),
Abbildung 3: Die Anwendungsdaten liegen bereits im praktischen Json-Format vor.

Abbildung 3: Die Anwendungsdaten liegen bereits im praktischen Json-Format vor.

Abbildung 4: Das Wrapperskript »get_data.py« holt Daten aus dem Internet.

Abbildung 4: Das Wrapperskript »get_data.py« holt Daten aus dem Internet.

Zeile 2 bindet das in Zeile 5 benötigte Python-Modul »urllib2« ein. Zeile 4 schreibt die erforderliche Mimetype-Angabe über den Befehl »print« in die Standardausgabe, hier »application/json« . Zeile 5 liest anschließend die Erdbebendaten unter der angegebenen URL, um sie analog zur vorherigen Zeile auszugeben. Der Webserver liefert die in die Standardausgabe geschriebenen Daten als Antwort an den Report zurück (mit Ziffer 3 in Abbildung 4 markiert).

Die eigentliche Beispielanwendung startet nach dem Aufruf der HTML-Datei aus Listing 4 im Browser. Der zugehörige Code bindet in den Zeilen 3 bis 11 alle nötigen Dateien ein. Dazu gehören das React-Framework (Zeile 6), der JSX-Übersetzer (Zeile 7), Jquery (Zeile 8), die Anwendung selbst (Zeile 9) sowie einige Hilfsfunktionen (Zeile 10). Der Wert »text/jsx« im Type-Attribut aus Zeile 9 veranlasst den JSX-Übersetzer dazu, die Datei »report.js« im Vorübergehen zu übersetzen. Im Produktiveinsatz sollten Entwickler Dateien mit JSX-Code zuvor besser über das Node-Modul »jsxc« [11] übersetzen.

Listing 4

index.html

01 <!DOCTYPE html>
02 <html>
03 <head>
04     <title>Erdbeben-Report</title>
05     <link rel="stylesheet" href="css/app.css"/>
06     <script src="http://fb.me/react-0.11.1.js"></script>
07     <script src="http://fb.me/JSXTransformer-0.11.1.js"></script>
08     <script src="http://code.jquery.com/jquery-1.10.0.min.js"></script>
09     <script type="text/jsx" src="js/report.js"></script>
10     <script src="js/utilities.js"></script>
11 </head>
12 <body></body>
13 </html>

Nach dem Initialisieren lädt die App mit Hilfe der Jquery-Methode »getJSON()« die Erdbebendaten (Listing 5). Der Methodenaufruf übernimmt im ersten Parameter die URL des Wrapperskripts »get_data.py« und im zweiten die Rückruffunktion, die er nach dem erfolgreichen Laden der Json-Daten aufruft. Letztere speichert das Javascript-Objekt, das durch Parsen des geladenen Json-Dokuments entsteht, in der Variablen »data« und reicht das Objekt dann in Zeile 3 an die React-Methode »renderComponent()« weiter. Diese ruft nun ihrerseits die Renderfunktion der Komponente »Report()« auf und gibt den Report im Browser aus.

Listing 5

report.js (Auszug)

01 $(document).ready(function() {
02         $.getJSON("./cgi-bin/get_data.py", function(data) {
03                 React.renderComponent(<Report items={data}/>, document.body);
04         });
05 });

Die React-Komponente »Report()« hatte bereits in Listing 2 einen Auftritt. Sie enthält neben der Renderfunktion (Zeile 12) die beiden Methoden »getInitialState()« (Zeile 2) und »handleUserInput()« (Zeile 7). Erstere ruft React intern auf, sie übergibt die Anfangswerte der Statusvariablen. Zeile 4 wählt den anfänglichen Standort aus. Das ist in diesem Fall die isländischen Hauptstadt Reykjavík, die nur wenige Kilometer vom aktiven Vulkan Bardabunga entfernt liegt.

Die Kind-Komponente »ReportOptions« verwendet die Methode »handlerUserInput()« (Listing 2, Zeile 7), um Standorte zu verschieben. Sie übernimmt im ersten Parameter den Breiten- und im zweiten den Längengrad. Nach dem Speichern über »setState« (Zeile 8) erstellt die Anwendung den Report neu.

»Report« bindet in den Zeilen 16 bis 19 von Listing 2 die Komponente »ReportOptions« ein und übergibt ihr den aktuellen Standort und die Methode »handleUserInput()« . In den Zeilen 20 bis 23 integriert »Report« anschließend mit »ReportTable« eine weitere Komponente und reicht ihr ebenfalls den aktuellen Standort weiter (Zeile 21). Zeile 22 empfängt dann die Anwendungsdaten, die Zeile 3 in Listing 5 generiert.

Listing 6 zeigt die Komponente »ReportOptions« , die der Entwickler in den Zeilen 16 bis 19 von Listing 2 eingebunden hat. Ihre Renderfunktion ab Zeile 32 in Listing 6 erzeugt in den Zeilen 36 bis 42 sowie 44 bis 50 die Textfelder zur Auswahl des Standorts und zusätzlich den Button, über den die Anwendung den Standort des Users ermittelt. Die Textfelder lesen ihre Werte über das Attribut »value« aus den Eigenschaften »this.props.position.latitude« (Zeile 38) beziehungsweise »this.props.position.longitude« (Zeile 46).

Listing 6

report.js (Auszug)

01 var ReportRow = React.createClass({
02     render: function() {
03     return (
04         <tr>
05             <td>{dateFormatString(this.props.item.date_time)}</td>
06             <td><a href={this.props.item.link}>{strUpperWords(this.props.item.location)}</a></td>
07             <td>{Math.round(this.props.item.distance)}</td>
08             <td>{datePassedHours(this.props.item.date_time)}</td>
09             <td>{this.props.item.magnitude}</td>
10             <td>{Math.round(this.props.item.depth)}</td>
11         </tr>
12         );
13     }
14 });
15 [...]
16 var ReportOptions = React.createClass({
17     handleChange: function() {
18         this.props.onUserInput(<
19             this.refs.latitude.getDOMNode().value,
20             this.refs.longitude.getDOMNode().value
21         );
22     },
23     handlePosition: function() {
24         var that = this;
25         navigator.geolocation.getCurrentPosition(function(pos) {
26             that.props.onUserInput(
27                     Math.round(pos.coords.latitude*100)/100,
28                     Math.round(pos.coords.longitude*100)/100
29             );
30         }, function(err) { console.log(err) });
31     },
32     render: function() {
33         return (
34             <form>
35                 Breitengrad:&nbsp;
36                 <input
37                     type="number"
38                     value={this.props.position.latitude}
39                     ref="latitude"
40                     step="any"
41                     onChange={this.handleChange}
42                 />
43                 &nbsp;Längengrad:&nbsp;
44                 <input
45                     type="number"
46                     value={this.props.position.longitude}
47                     ref="longitude"
48                     step="any"
49                     onChange={this.handleChange}
50                     />
51                 &nbsp;
52                 <input
53                         type="button"
54                         value="Aktueller Standort"
55                         ref="postion"
56                         onClick={this.handlePosition}
57                 />
58             </form>
59         );
60     }
61 });

Ändert der Benutzer den Wert eines Textfelds, ruft React über das Attribut »onChange« (Zeilen 41 und 49) die Rückruffunktion »handleChange()« (Zeile 17) auf. Die holt sich die geänderten Werte jeweils über das Attribut »value« und die Methode »getDOMNode()« und reicht sie an »handlePosition()« (in Listing 2, Zeile 7) weiter.

Das Attribut »ref« in Listing 6 (Zeilen 39 und 47) erzeugt die Referenzen »this.refs.latitude« (Zeile 19) sowie »this.refs.longitude« (Zeile 20). Ein Klick auf den Knopf (Zeile 52) ruft die Methode »handlePosition()« ab Zeile 23 auf. Sie kommt als Rückruffunktion zum Einsatz, sobald der Nutzer den Knopf drückt, was das Attribut »onClick« in Zeile 56 bemerkt. Im Erfolgsfall reicht die asynchrone Methode »getCurrentPosition()« (Zeile 25) innerhalb von »handlePosition()« die Koordinaten in der Variablen »pos« weiter.

Die gerundeten Werte übergibt Listing 2 in den Zeilen 7 bis 11 an »handleUserInput()« . Die Komponente »ReportTable« aus Listing 1 steckt die Anwendungsdaten in eine Tabelle. Über »geoSort()« in Zeile 5 sortiert die Renderfunktion die Datensätze nach ihrer Entfernung zum Standort. Anschließend ruft sie über die »forEach« -Schleife die Komponente »ReportRow« (Zeile 7) auf und generiert für jeden Datensatz eine eigene Tabellenzeile. Ebenfalls in Zeile 7 hängt die Funktion »geoSort()« die HTML-Fragmente an das Feld »rows« . Zeile 6 berechnet zuvor noch den Abstand zwischen dem Datensatz und dem Standort.

Listing 6 zeigt die Komponente »ReportRow« (Zeile 1). Sie erzeugt für einen Datensatz aus »this.props.item« je eine Tabellenzeile im HTML-Format. Die Tabelle enthält die Spalten Datum, Ort, räumliche und zeitliche Entfernung, Magnitude sowie Tiefe des Erdbebens.

Fluxbau

Flux ist ein Entwurfsmuster für React-Anwendungen und stattet diese mit einem gerichteten, kreisförmigen Datenfluss aus (Abbildung 5). Flux zerlegt eine Anwendung dazu in vier Teile, die unterschiedliche Funktionen besitzen. Nach dem Muster zerlegt Flux auch den Javascript-Code und speichert ihn wie in Listing 7.

Listing 7

Von Flux erzeugte neue Verzeichnisstruktur

01 $ ls -R flux
02 flux
03  |- ...
04  |-js
05  |  |- action:
06  |  |  |- ReportAction.js
07  |  |- components
08  |  |  |- Report.react.js
09  |  |  |- ReportOptions.react.js
10  |  |- dispatcher
11  |  |  |- AppDispatcher.js
12  |  |- stores
13  |  |  |- AppStore.js
Abbildung 5: So sieht die Arbeitsteilung dank des Flux-Modells aus.

Abbildung 5: So sieht die Arbeitsteilung dank des Flux-Modells aus.

Formt der Entwickler die Beispielanwendung mit Hilfe von Flux um, würde Flux den Code, der den Standort mittels Geolocation-API ermittelt, in eine Funktion auslagern und im Unterordner »action« speichern. Die oben beschriebenen Komponenten würde Flux unter »components« ablegen.

Der Dispatcher ist eine Erfindung von Flux. Er lauscht auf Aktionen und gibt deren Resultate an alle Stores weiter, die sich beim Dispatcher abgemeldet haben. Die Stores landen im gleichnamigen Verzeichnis »stores« . Sie enthalten das Datenmodell inklusive seiner Geschäftslogik. Im Fall der Beispielanwendung betrifft das die Erdbebendaten und die Sortierfunktion »geoSort()« . Ändert sich das Datenmodell, aktualisiert der Store die Browseransicht, indem er wie gehabt die Methode »setState()« aufruft.

Listing 8 zeigt schließlich einen einfachen Store, wobei »counter« in Zeile 2 das Datenmodell speichert – einen Zähler. Die Methode »callback()« (Zeile 3) inkrementiert das Modell und aktualisiert die View in React über die Rückruffunktion »reactCallback()« . Die Zeilen 8 bis 10 registrieren die Methode »callback()« beim Dispatcher aus Listing 9. Dieser fügt »callback()« in den Zeilen 3 bis 5 seiner Liste mit Rückruffunktionen hinzu. Adressiert nun eine Aktion die Methode »raise()« (Zeile 6), so leitet diese das Ereignis an alle registrierten Rückruffunktionen weiter.

Listing 8

Beispiel-Store

01 var Store = {
02     counter: 0,
03     callback: function(event) {
04         reactCallback(++this.counter);
05     }
06 };
07
08 Dispatcher.register(function(event) {
09     Store.callback(event);
10 });

Listing 9

Dispatcher

01 var Dispatcher = {
02     cbks: [],
03     register: function(cbk) {
04         this.cbks.push(cbk);
05     },
06     raise: function(event) {
07         var i = this.cbks.length;
08         while(i--) {
09             this.cbks[i](event);
10         }
11     }
12 };

Fazit

Die Renderfunktionen sind das große Plus von React. Sie sind simpel und lassen sich wiederverwenden. Dank des One-Way-Binding von React kann der Entwickler jederzeit den Status der Anwendung kontrollieren. Flux kultiviert React in positiver Weise, indem es erlaubt, die Abläufe gezielt zu steuern und den Code nach seiner Funktion aufzudröseln. Spannend bleibt, wohin die Entwickler ihre Daumen in Zukunft neigen: in Richtung Angular, React, Meteor oder doch in Richtung eines bis dato unbekannten Framework.

Infos

  1. React: http://facebook.github.io/react/
  2. Tim Schürmann, “Reaktionsfreudig”: Linux-Magazin, 06/2014, S. 28
  3. Angular.js: https://angularjs.org
  4. Meteor: https://www.meteor.com
  5. Flux: https://github.com/facebook/flux/
  6. Der Erdbeben-Report: http://pamoller.com/erdbeben-report/
  7. Geolocation-API: http://www.w3.org/TR/geolocation-API/
  8. JSX für React: http://facebook.github.io/react/docs/jsx-in-depth.html
  9. Cross Origin Resource Sharing: http://enable-cors.org
  10. Earthquake-Report: http://earthquake-report.com
  11. JSX-Tool für Node.js: https://www.npmjs.org/package/jsxc
  12. Komplette Listings zum Artikel: https://www.linux-magazin.de/static/listings/magazin/2014/12/react/
DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 5 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben