11. React

Wie auch Angular ist React ein Komponenten-System. React ist eine reine UI-Bibliothek, kein vollständiges Rahmenwerk. Es ist bei Facebook entstanden und hat damit eine zügige Markteroberung geschafft, weil es von Anfang an eine gewissen Reife und einen starken Sponsor hatte.

11.1 Merkmale

React realisiert wiederverwendbare, statusbehaftete und interaktive Komponenten. Es ist schlank und schnell. Die Lernkurve ist signifikant geringer als bei Angular und oft lassen sich bereits nach wenigen Stunden brauchbare Ergebnisse erzielen. React hat eine client- und eine serverseitige Render-Engine. Das heißt, dass die dynamischen Seiten auch auf dem Server gerendert werden können. Das ist aber nur eine Option. Diese Wahlfreiheit ist ein deutlicher Vorteil bei der Optimierung komplexer Anwendungen.

Herausragendes Merkmal ist der virtuelle DOM (den aber auch Angular hat). Damit wird der aktive Teil der HTML-Seite im Speicher zusammegebaut und dann wird die Übergabe an den Browser zu einem wohldefinierten Zeitpunkt erfolgen.

Ein weiteres Merkmal ist die Sprache JSX für Vorlagen. Im Zusammenhang mit TypeScript wird TSX benutzt. Jede andere Vorlagensprache nutzt primär HTML, in welches dann aktive Elemente eingestreut werden, also Schnippsel mit C#, JavaScript oder Ruby etc. JSX dreht erstmals dieses Prinzip um und es wird alles in reinem JavaScript geschrieben. In dieses JavaScript wird dann einfach HTML geschrieben – ohne Anführungszeichen oder sonstige lästige Formatierprobleme.

11.1.1 Tipps

Wenn Du Chrome oder Firefox als Browser benutzt, lohnt die Integration eines speziellen Werkzeugs für React:

Abbildung: React Tools für Chrome
Abbildung: React Tools für Chrome

11.1.2 Das virtuelle DOM

Das virtuelle DOM ist eine Art Schattenobjektmodell im Speicher. Statt die HTML-Element direkt zu bearbeiten, wird alles als Objekt in JavaScript gehalten. Hintergrund ist die Eigenheit aller Browser, bei Änderungen jeglicher Eigenschaft oder an Elementen, die Seite neu zu zeichnen. Diese Fähigkeit ist essenziell für Animationen, Spiele und Interaktionen. Der Browser kann und wird dies also niemals unterlassen. Wenn aber eine Schleife 134 Zeilen zu einer Tabelle hinzufügt und diese jeweils zehn Zellen hat und in jeder 5 Elemente sind, dann sind dies 6.700 Zeichenvorgänge. Was auf dem Desktop noch nicht auffallen muss, wird ein mobiles Gerät ins Schwitzen bringen. Zumindest aber ist dann der Akku leer. Direkte Operationen auf dem DOM sind also grundsätzlich riskant. Statt nun immer wieder angepasste Lösungen zu basteln, regelt React dies zentral.

11.2 Erste Schritte

Eine React-Applikation benötigt drei Skripte und einen Einstiegspunkt in JavaScript. JSX benötigt noch Babel – React nutzt die Vorlagensprache JSX, die übersetzt werden muss. Mit JSX ist es nicht ganz so einfach. Die Übersetzung kann auf dem Server oder im Browser erfolgen. Ältere Quellen schlagen JSXTransform oder react-tools vor. Facebook hat aber seit einiger Zeit entschieden, hier Babel zu nutzen. Babel wurde bereits im Kapitel Werkzeuge > Babel kurz vorgestellt.

Im einfachsten Fall sieht dies folgendermaßen aus:

Listing: supersimple.html im Demo-Project react
 1 <!DOCTYPE html> 
 2 <html> 
 3   <head> 
 4    <script src="bower_components/react/react.js"></script> 
 5    <script src="bower_components/react/react-dom.js"></script> 
 6    <script src="bower_components/babel-standalone/babel.js"></script\
 7 > 
 8   </head> 
 9   <body> 
10    <div id="myDiv"></div> 
11    <script type="text/babel"> 
12      ReactDOM.render(<h1>Hallo React!</h1>, 
13               document.getElementById('myDiv') );   
14    </script> 
15   </body> 
16 </html>

Die Skripte wirst du dir entweder von Bower oder npm beschaffen. Heute ist npm praktisch konkurrenzlos.

11.2.1 Professionelle Einrichtung mit npm

Bei npm sieht es alternativ folgendermaßen aus:

npm i --save react react-dom
npm i --save-dev babel 
npm i --save-dev -g babel-cli

Babel verfügt hier neben den erforderlichen Modulen auch über eine Kommandozeile (cli = command line interface). Ein erste, sehr einfache Applikation besteht aus der HTML-Startseite und einer JavaScript-Datei.

 1 <!doctype html>
 2 <html lang="en">
 3 
 4 <head>
 5     <meta charset="utf-8">
 6     <title>React</title>
 7     <script src="./bower_components/react/react.js"></script>
 8     <script src="./bower_components/react/react-dom.js"></script>
 9 </head>
10 
11 <body>
12     <div id="app">
13     </div>
14     <script src="./assets/js/index.js"></script>
15 </body>
16 
17 </html>

Die erste und einfachste React-Applikation sieht nun folgendermaßen aus:

1 ReactDOM.render(
2   <h1>Hello, world!</h1>,
3   document.getElementById('app')
4 );

Dies kann der Browser freilich nicht verarbeiten, weshalb die Kommandozeile zum Einsatz kommt. Das Programm wird nun übersetzt, in dem auf der Kommandozeile, im Stammverzeichnis des Projekts, folgendes eingegeben wird:

1 babel index.js --preset react > .\assets\js\index.js

Der Zielpfad ist hier nur ein Vorschlag – der guten Ordnung halber – und deutet schon den Weg an, wie das Projekt später strukturiert wird. Mit dem Aufruf entsteht die JavaScript-Datei, die der Client alleine verarbeiten kann. Dies sieht etwa folgendermaßen aus:

1 ReactDOM.render(React.createElement(
2   'h1',
3   null,
4   'Hello, world!'
5 ), document.getElementById('app'));

Nun gilt es noch, das Programm auszuprobieren.

11.2.2 Testserver einrichten

Um einen Webserver zum Ausprobieren zu bekommen, ist nicht viel notwendig. NodeJS wird ohnehin benutzt und damit steht ein passendes Modul schnell zur Verfügung. Installiere einen Server im Projekt:

npm install http-server --save-dev

Rufe dann den Server einfach auf:

http-server

Der Server bindet sich standardmäßig an alle Netzwerkschnittstellen und nutzt den Port 8080. Der muss natürlich noch verfügbar sein. Alternativ lässt sich jeder Port festlegen, beispielsweise mit -p 3001 der Port 3001.

Abbildung: Start des Moduls http-server und erste Ausgaben
Abbildung: Start des Moduls http-server und erste Ausgaben

Mehr Informationen zum Server und Optionen zur Konfiguration sind bei npm zu finden.

11.3 Dienste

React verfügt über keine eigenen AJAX-Funktionen. Es wird empfohlen, hier auf eine ebenso schlanke und einfache Bibliothek auszuweichen, die AJAX etabliert hat: jQuery. Speziell der Abschnitt zu AJAX ist hier von Bedeutung. Speziell auf React zugeschnittene Anpassungen gibt es hier nicht. Die konkrete Implementierung richtet sich eher nach den Anforderungen der Dienste bzw. des Servers.

11.4 Komponenten erstellen

In React dreht sich alles um Komponenten. Das Projekt musst du also in verschachtelte Bausteine zerlegen, die solange vereinfacht werden, bis sich sinnvolle, fassbare und gut lesbare Code-Schnippsel ergeben. Dazu gehört freilich ein bisschen Erfahrung.

Komponenten haben viele Optionen, von denen nur eine zwingend erforderlich ist: render. Die einfachste Komponente könnte deshalb folgendermaßen aussehen:

1 var MyComponent = React.createClass({ 
2   render: function() { 
3     return ( <h1>Hallo React!</h1> ); 
4   } 
5 });

Im Gegensatz zum Einführungsbeispiel gibt es hier nur eine JavaScript-Datei. Der Name der Komponente ist der Name der Variablen, MyComponent. Im Hauptprogramm muss diese Komponente nun im HTML platziert und gerendert werden. Dies würde nun folgendermaßen aussehen:

1 ReactDOM.render( 
2    <MyComponent/>,          
3    document.getElementById('myDiv') 
4 );

Das Tag MyComponent wird nun im div-Element mit dem Identifizierer myDiv platziert. Für statisches HTML ist dies freilich umständlich, das hätte man auch einfacher haben können. Aber es ergeben sich Vorteile:

Eine Komponenten-Architektur zu entwickeln erfordert eine bestimmte Vorgehensweise. Diese Liste kann dir als Anregung dienen:

Ein Maßstab ist beispielsweise die Größe. Eine Komponente sollte im Editor deiner Wahl maximal eine Seite füllen. Wenn du viel scrollen musst, ist sie zu groß (es gibt Ausnahmen, natürlich).

11.4.1 Dynamische Inhalte

Wie viele anderen Vorlagensprachen nutzt auch React sogenannte Handlebars (benannte nach den optisch entfernt an Fahrradgriffe erinnernden Klammern) für dynamische Bausteine:

1 var MyComponent = React.createClass({ 
2    render: function() { 
3      return ( <h1>Hallo, {this.props.name}!</h1> ); 
4    } 
5 }); 
6 
7 ReactDOM.render(<MyComponent name="Coder" />, 
8                 document.getElementById('myDiv'));

Hier wird in Zeile 3 auf eine Eigenschaft props und dort auf das Objekt name zugegriffen. Eigenschaften einer React-Komponente stehen immer in props. Die Eigenschaft können sich beliebig ändern, erst mit dem Aufruf von render erfolgt die Übertragung an das DOM des Browsers. Dieser Prozess ist gut kontrolliert und das ist der Schlüssel zur Performance einer React-Anwendung. Du hast Kontrolle über die Abläufe oder, in der Softwaresprache, die Lebensdauer.

11.4.2 Lebensdauer

Die Lebensdauer einer Komponente kontrolliert, wann was beim Abarbeiten passiert. Die Wichtigsten sind:

Es gibt weitere für spezielle Zwecke, die hier am Anfang noch nicht betrachtet werden sollen.

11.4.3 Status

Eine Komponente befindet sich jederzeit in einem spezifischen Zustand – dem Status. Der Zustand der Komponente definiert Standardwerte und Fallbacks, falls die Umgebung keine Werte liefert. Drei Funktionen dienen der Steuerung des Status:

Der Status wird in der Eigenschaft stats gehalten. Das ist das Objekt, das mit der Komponente verbunden ist. Der Zugriff auf setState löst den Aktualisierungsvorgang und damit das Rendern aus.

 1 var MyComponent = React.createClass({ 
 2    getInitialState: function(){ 
 3      return { 
 4        count: 5 
 5      } 
 6    }, 
 7    render: function(){ 
 8      return ( <h1>{this.state.count}</h1> ) 
 9    } 
10 });

Der Datenfluss ist standardmäßig unidirektional via stats und props.

11.4.4 Ereignisse

Zuständen lassen sich aufgrund von Aktionen ändern.

 1 var Counter = React.createClass({ 
 2   incCount: function(){ 
 3     this.setState({ 
 4       count: this.state.count + 1 
 5     }); 
 6   }, 
 7   getInitialState: function(){ 
 8     return { count: 0 } 
 9   }, 
10   render: function(){ 
11     return ( <div class="somecomponent"> 
12                <h1>Count: {this.state.count}</h1> 
13                <button type="button" onClick={this.incCount}>Increme\
14 nt</button> 
15              </div> ); 
16   } 
17 }); 
18 ReactDOM.render(<Counter/>, document.getElementById('myDiv'));

11.5 Listen und Tabellen

Listen sind der Einstieg in Schleifen, also der wiederholten Ausgabe derselben Vorlage mit verschiedenen Werten.

11.5.1 Einfache Listen

Ausgangspunkt ist wieder die Erstellung des HTML. Einfache Transformationsfunktionen in JavaScript, wie beispielsweise map, sind hierfür ideal:

1 const numbers = [1, 2, 3, 4, 5];
2 const listItems = numbers.map((number) =>
3   <li>{number}</li>
4 );

Hier wird das Array durchlaufen und eine Reihe von List-Elementen li erstellt. Das Rendern setzt dann darauf auf:

1 ReactDOM.render(
2   <ul>{listItems}</ul>,
3   document.getElementById('myDiv')
4 );

Es ist sinnvoll, wenn du solche Gebilde gleich in eine eigene Komponente verpackst:

 1 function NumberList(props) {
 2   const numbers = props.numbers;
 3   const listItems = numbers.map((number) =>
 4     <li>{number}</li>
 5   );
 6   return (
 7     <ul>{listItems}</ul>
 8   );
 9 }
10 
11 const numbers = [1, 2, 3, 4, 5];
12 ReactDOM.render(
13   <NumberList numbers={numbers} />,
14   document.getElementById('myDiv')
15 );

11.5.2 Listen mit Schlüsselwerten

Stammen die Daten aus einer Datenbank, wird oft ein Primärschlüssel dabei sein, der später die Zuordnung weiterer Daten erlaubt. Der Schlüssel muss separat gehalten werden, für den Benutzer ist dies jedoch uninteressant. Dazu wird der Wert in einer weiteren Eigenschaft gehalten, üblicherweise mit dem Namen key.

 1 function ListItem(props) {
 2   return <li>{props.value}</li>;
 3 }
 4 
 5 function NumberList(props) {
 6   const numbers = props.numbers;
 7   const listItems = numbers.map((number) =>
 8     <ListItem key={number.id}
 9               value={number.val} />
10   );
11   return (
12     <ul>
13       {listItems}
14     </ul>
15   );
16 }
17 
18 const numbers = [
19   { id: 1, val: "Eins"},
20   { id: 2, val: "Zwei"},
21   { id: 3, val: "Drei"},
22   { id: 4, val: "Vier"}];
23 ReactDOM.render(
24   <NumberList numbers={numbers} />,
25   document.getElementById('root')
26 );

Manchmal ist es lästig, ein Hilfsfunktion nur für den Aufruf von map zu erstellen. In solchen Fällen ist das Einbetten in JSX möglich.

 1 function NumberList(props) {
 2   const numbers = props.numbers;
 3   return (
 4     <ul>
 5       {numbers.map((number) =>
 6         <ListItem key={number.toString()}
 7                   value={number} />
 8       )}
 9     </ul>
10   );
11 }

Liegt der Verdacht nahe, dass der Code im JSX komplexer werden könnten (Tipp: Er wird es), dann ist aber eher davon abzuraten. Die Mischung aus HTML und JavaScript wird dann sehr schnell sehr unübersichtlich.

11.6 Wiederverwendbare Komponenten

Wiederverwendbare Komponenten sind Komponenten, die wie eine Funktion im Code an verschiedenen Stellen eingesetzt werden können. Sie sind nicht spezifisch für eine bestimmte Situation, sondern für eine abstrakte Aufgabe.

11.6.1 Einfache Komponenten

Eine komponentenbasierte Benutzeroberfläche könnte etwa folgendermaßen aussehen:

1 <AdvancedTextBox> 
2   <TextAreaWithLimit limit={180} /> 
3   <RemainingCharacters /> 
4   <SaveButton /> 
5 </AdvancedTextBox>

Hier enthält eine Komponente eine Liste weiterer Komponenten. Die Definition ist verhältnismäßig einfach:

 1 class SaveButton extends React.Component { 
 2   render() { 
 3     // Schaltfläche
 4   } 
 5 } 
 6 
 7 class RemainingCharacters extends React.Component { 
 8   render() { 
 9     // Anzeige restlicher Zeichen 
10   } 
11 } 
12 
13 class TextAreaWithLimit extends React.Component { 
14   render() { 
15     // TextArea
16   } 
17 } 
18 
19 class AdvancedTextBox extends React.Component { 
20   render() { 
21     return ( 
22       <div> 
23         { this.props.children } 
24       </div> ); 
25   } 
26 }

Unter einfach ist hier zu verstehen, dass die Struktur des Objektmodells der Elemente quasi unveränderlich ist. React hält die Struktur vor und entscheidet über die Renderstrategie anhand der Position im DOM. Wenn nun die Schaltfläche dynamisch ausgeblendet oder eingeblendet werden soll, und dies über eine Veränderung des Objektmodells der Komponente erfolgt, kommt React hier aus dem Tritt. Natürlich kannst du die Schaltfläche mit CSS einfach unsichtbar machen, aber das Entfernen aus dem DOM ist bei größeren Projekten durchaus sinnvoll und bringt Leistungsvorteile. Wird eine Liste von Elementen gerendert, die weitgehend dynamisch ist, muss der Schlüsselwert mit dem speziellen Attribut key mitgeführt werden. Dies hilft bei der Organisation der Render-Vorgänge.

11.6.2 Erweiterte Komponenten

In Zeile 5 im folgenden Listing ist die Vorgehensweise zu finden. Im Grunde gibt es eine einfache Regel, wann key erforderlich ist: Wenn die Datenquelle ein Array ist, dann kann dies in der Regel auch manipuliert werden. In solchen Fällen ist das Schlüsselattribut zwingend erforderlich.

Listing: Produktliste mit der Komponente Product
 1 class ProductList extends React.Component { 
 2   render() { 
 3     return ( 
 4       <div> {this.products.map(product => 
 5         <Product key={product.id} product={product} />)
 6         } 
 7       </div>
 8     ); 
 9   } 
10 }

11.7 Formulare

Formulare in React werden wie üblich mit HTML erstellt. Im Gegensatz zum einfachen HTML wird aber das Absenden der Daten nicht dem Browser überlassen, sondern mit einem entsprechenden Ereignisbehandler verbunden – also einer Funktion in React. Der Code zum Erstellen eines Formulars ist wie bei React üblich eine Komponente.

11.7.1 Standard-Elemente

Ein typischer Aufbau eines React-Formulars sieht folgendermaßen aus:

 1 class NameForm extends React.Component {
 2   constructor(props) {
 3     super(props);
 4     this.state = {value: ''};
 5 
 6     this.handleChange = this.handleChange.bind(this);
 7     this.handleSubmit = this.handleSubmit.bind(this);
 8   }
 9 
10   handleChange(event) {
11     this.setState({value: event.target.value});
12   }
13 
14   handleSubmit(event) {
15     // Dienstfunktion aufrufen
16     event.preventDefault();
17   }
18 
19   render() {
20     return (
21       <form onSubmit={this.handleSubmit}>
22         <label>
23           Name:
24           <input type="text" value={this.state.value} 
25                  name="name" onChange={this.handleChange} />
26         </label>
27         <input type="submit" value="Submit" />
28       </form>
29     );
30   }
31 }

Hier wird das onSubmit-Ereignis abgefangen und zum Aufruf eines Dienstes benutzt. Der Übersichtlichkeit halber ist der Dienst nicht in der Komponente selbst. Der Wert des Formulars – hier das Textfeld name – wird über den Status der Komponente gebunden. Wenn sich Änderungen an den Werten ergeben, wird der Benutzer diese nicht sehen, weil die Eingabe im virtuellen DOM landet. Soll der Wert sofort gespiegelt werden, muss auf jede Änderung reagiert werden. Dies macht der handleChange-Handler im Code, wo seinerseits setState aufgerufen wird.

11.7.1.1 Ereignisse

Ereignisse in React sind nicht exakt reine JavaScript-Ereignisse. Es sind sogenannte synthetische Ereignisse. Die Schreibweise ist dabei zu beachten: benutzt wird immer Camel-Case. Statt wie in JavaScript onclick musst du hier nun onClick schreiben (großes “C”). Der Code kann direkt eingebunden werden:

1 <div onClick={se => console.log(se.target.textContent)} > 
2   Der Text im Element... 
3 </div>

Der Name der Parametervariablen ist frei wählbar. Der Name se im Beispiel deutet “synthetic event” an. Neben der Kapselung kümmert sich React auch um die immer noch präsenten kleinen Unterschiede der Browser, um die du dich selbst kümmern müsstest, wenn JavaScript direkt benutzt wird. Ein typisches Beispiel ist onchange, dass sehr uneinheitlich auf Copy&Paste-Aktionen, programmatische Inhaltsänderungen und beim Austausch von input type="text" versus textarea reagiert. Mit React und onChange funkioniert es immer.

Einige Beispiele für Ereignisse in React sind:

Es gibt weitere im DOM für die Zwischenablage, Benutzerumgebung, Mausrad, Kompositionen, Medien und Bilder. JavaScript kennt noch mehr. Fehlt etwas, dann kann ein natives Ereignis dennoch benutzt werden. Dazu wird im Schritt componentDidMount der Aufruf addEventListener benutzt. Entfernt wird der Handler bei Bedarf mit removeEventListener im Lebenszyklusschritt componentWillUnmount.

11.7.2 Textarea

Auf den ersten Blick erscheint React nur als sehr flache Schicht oberhalb des HTML. Das ist durchaus so und auch beabsichtig, soll es sich doch um eine schlanke und schnelle Bibliothek handeln. Das HTML-Element textarea ist da ein Ausnahme. Im HTML ist der Wert der Inhalt, nicht wie sonst das Attribut value. Hier abstrahiert React das Element und sorgt für einen vereinheitlichten Zugriff:

 1 class EssayForm extends React.Component {
 2   constructor(props) {
 3     super(props);
 4     this.state = {
 5       value: 'Dies ist der Standardwert.'
 6     };
 7 
 8     this.handleChange = this.handleChange.bind(this);
 9     this.handleSubmit = this.handleSubmit.bind(this);
10   }
11 
12   handleChange(event) {
13     this.setState({value: event.target.value});
14   }
15 
16   handleSubmit(event) {
17     // Der Dienstaufruf
18     event.preventDefault();
19   }
20 
21   render() {
22     return (
23       <form onSubmit={this.handleSubmit}>
24         <label>
25           Name:
26           <textarea value={this.state.value} 
27                     onChange={this.handleChange} />
28         </label>
29         <input type="submit" value="Submit" />
30       </form>
31     );
32   }
33 }

Gestalterische Maßnahmen wie Zeilen und Spalten wurden hier nicht benutzt.

11.7.3 Listenelemente

Listen und Auswahlfelder werden mit select erstellt. Das Formular sieht dann folgendermaßen aus:

 1   render() {
 2     return (
 3       <form onSubmit={this.handleSubmit}>
 4         <label>
 5           Pick your favorite La Croix flavor:
 6           <select value={this.state.value} 
 7                   onChange={this.handleChange}>
 8             <option value="grapefruit">Grapefruit</option>
 9             <option value="lime">Lime</option>
10             <option value="coconut">Coconut</option>
11             <option value="mango">Mango</option>
12           </select>
13         </label>
14         <input type="submit" value="Submit" />
15       </form>
16     );

Der übrige Code ist unverändert gegenüber den vorherigen Beispielen.

11.7.4 Komplexe Formulare

Formulare mit mehreren Feldern sehen nicht anders aus. Während der Wert jeden Feldes an eine Eigenschaft des Status gebunden wird, gibt es normalerweise nur einen Handler für Änderungen, der immer wieder benutzt wird:

 1 render() {
 2   return (
 3     <form>
 4       <label>
 5         Is going:
 6         <input
 7           name="isGoing"
 8           type="checkbox"
 9           checked={this.state.isGoing}
10           onChange={this.handleInputChange} />
11       </label>
12       <br />
13       <label>
14         Number of guests:
15         <input
16           name="numberOfGuests"
17           type="number"
18           value={this.state.numberOfGuests}
19           onChange={this.handleInputChange} />
20       </label>
21     </form>
22   );
23 }

Nun können sich die Elemente aber unterscheiden. Im Beispiel wird ein Kontrollkästchen benutzt. In der Funktion wird darauf reagiert:

 1 handleInputChange(event) {
 2   const target = event.target;
 3   const value = target.type === 'checkbox' ? target.checked 
 4                                            : target.value;
 5   const name = target.name;
 6 
 7   this.setState({
 8     [name]: value
 9   });
10 }

Die Schreibweise [name]: value ist ES2015-Code und verkürzt den Aufruf einer Eigenschaft. Babel kann damit umgehen. In normalem JavaScript würde man das folgendermaßen schreiben:

1 var partialState = {};
2 partialState[name] = value;
3 this.setState(partialState);

Die Funktion setState ist hier so schlau, nur die Änderungen zu übertragen und nicht den gesamten Status zu überschreiben, auch wenn hier nur ein einzelner Wert angeliefert wird. Der Aufruf des Handlers erfolgt immer feldweise (der Benutzer kann immer nur ein Feld zu einer Zeit bearbeiten). Das muss beim Schreiben der Werte berücksichtigt werden. Der gesamte Status ergibt dann den Wert des Formulars mit allen Feldern.

11.7.5 Validierung

Ein Formular ganz ohne Prüfung wird es eher selten geben. React hat hier erstmal keine eingebauten Funktionen. Mit Hilfe der Erweiterung react-validation wird eine weitere API bereitgestellt.

Hier ein Beispiel für ein einfaches Formular zur Eingabe einer E-Mail-Adresse:

 1 const EmailForm = React.createClass({ 
 2   handleClick() { 
 3     if (this.inputRef.checkValidity()) { 
 4       console.log(`Passt. Speichere ${this.inputRef.value}`); 
 5     } 
 6   }, 
 7   
 8   render() { 
 9     return ( 
10       <div> 
11         <input type="email" ref={inputRef => this.inputRef = inputRe\
12 f} /> 
13         <button onClick={this.handleClick}>Save</button> 
14       </div> 
15     ); 
16   } 
17 });
18 
19 ReactDOM.render(<EmailForm />, document.getElementById("react"));

Zuerst wird eine Referenz benötigt. Dies erledigt das Attribut ref (Zeile 11). Beim Klick-Ereignis wird dann das native DOM-Element beschafft. Die API stellt die Funktion checkValidity bereit.

Freilich ist die Validierung über das DOM-Element nicht optimal. Eine Abstraktion über den Status der Komponente ist oft der bessere Weg. Der direkte Zugriff erlaubt mehr Funktionen, aber gerade bei der Validierung wird davon kaum etwas benötigt. Eine kontrollierte Komponente entsteht, wenn das Ereignis über den Status verarbeitet wird und die Werte damit gebunden sind.

 1 const EmailForm = React.createClass({ 
 2   getInitialState() { 
 3     return { 
 4       currentEmail: this.props.currentEmail 
 5     }; 
 6   }, 
 7   setCurrentEmailState(se) { 
 8     this.setState({ 
 9       currentEmail: se.target.value 
10     }); 
11   }, 
12   handleClick() { 
13     console.log(`Neuen Wert speichern: ${this.state.currentEmail}`); 
14   }, 
15   render() {
16     return ( 
17       <div> 
18         <input type="email" 
19                value={this.state.currentEmail} 
20                onChange={this.setCurrentEmailState} /> 
21         <button onClick={this.handleClick}>Save</button> 
22       </div> 
23     ); 
24   } 
25 }) 
26 
27 ReactDOM.render( <EmailForm currentEmail="mark@fb.com" />, 
28                  document.getElementById("react") );

Hier wird bei Änderungen setCurrentEmailState aufgerufen. Hier wird der Status gesetzt – das Browser-DOM und Reacts virtuelles DOM sind jetzt immer synchron. Eine Element-Referenz ist nicht notwendig. Allerdings verlierst du damit auch den Zugriff auf die native HTML5-Funktion checkValidity. Eine eigene Prüfung ist nun notwendig. Dies ist reines JavaScript und wird hier nicht weiter betrachtet.

11.8 React und TypeScript

React wurde primär so entwickelt, dass JavaScript zum Einsatz kommt. Um zukunftssicher zu arbeiten, basieren viele Quellen und die originale Dokumentation auf der neuesten Version, ES6 (offizieller Name: ES2015/ECMAScript 2015). Da dies nicht jeder Browser gleichermaßen versteht, wird zur Übersetzung in allgemeingültiges JavaScript Babel eingesetzt. Die vorherigen Abschnitte haben dies hinreichend gezeigt.

Babel verfügt auch über die Fähigkeit, die Template-Sprache JSX zu übersetzen, die auf React abgestimmt ist.

Nun hat sich seit einiger Zeit TypeScript als Sprache etabliert. Neben dem Sprachumfang von ES6 sind weitere Funktionen enthalten. Die Sprache gilt auch als Muster für künftige JavaScript-Versionen wie ES7 und es gibt eine reichhaltige Unterstützung bei den Werkzeugen, was durchaus über das Angebot bei Babel hinausgeht.

Es lohnt sich also, React auch in Kombination mit TypeScript zu betrachten. Als Vorlagensprache kommt hier TSX zum Einsatz, was sich von JSX praktisch nicht unterscheidet, nur dass es vom TypeScript-Transpiler verarbeitet werden kann.

11.8.1 Einrichten

Zuerst wird TypeScript benötigt (global und für den Entwickler-Zweig):

$ npm install typescript --save-dev -g

Dann wird React eingerichtet (allgemein als Quelle):

$ npm install react react-dom --save

React liegt als (typlose) JavaScript-Bibliothek vor. Damit TypeScript das akzeptiert, müssen Typdefinitionen her. Diese werden unter der Gruppe @types in npm verfügbar gemacht. Dieser Schritt ist notwendig, weil TypeScript typsicher arbeitet (im Gegensatz zu Babel).

npm install @types/react @types/react-dom --save-dev

11.8.2 Projektstruktur

TypeScript-Projekte sollten einer festen Projektstruktur folgen. In Bezug auf React ist dies nicht anders. Da es einen Übersetzungsvorgang gibt, ist eine Trennung von Quellcode (TypeScript) und Zielcode (JavaScript) sinnvoll.

Eine einfache Struktur kennt mindestens zwei Ordner, dist und src (neben dem unvermeidlichen node_modules):

/
├─ node_modules/
├─ dist/
└─ src/
   ├─ services/
   ├─ config/
   └─ components/

Komponenten bestehen jeweils nur aus einer Datei, oder, wenn der Vorlagenteil sehr groß wird, aus zwei Dateien (comp.ts und comp.tsx). Das Quell-Verzeichnis src enthält am Anfang nur Komponenten. Die anderen Ordner werden erst bei größeren Applikationen benötigt.

In der Wurzel des Quellverzeichnisses liegt die Startseite, index.html.

Nun wird TypeScript konfiguiert. Dazu dient die Datei tsconfig.json:

Listing: tsconfig.json
 1 {
 2     "compilerOptions": {
 3         "outDir": "./dist/",
 4         "sourceMap": true,
 5         "noImplicitAny": true,
 6         "module": "commonjs",
 7         "target": "es5",
 8         "jsx": "react"
 9     },
10     "include": [
11         "./src/**/*"
12     ]
13 }

Beachte hier die Option "jsx": "react", die die Vorlagensprache aktiviert (und hier nicht “tsx” genannt wird).

11.8.3 Komponenten in TypeScript

Es ist nun bereits an der Zeit, eine erste Komponente zu entwickeln. Diese könnten beispielsweise folgendermaßen aussehen:

Listing: showUser.tsx in /src/components
1 import * as React from "react";
2 
3 export interface UserViewModel { 
4   firstName: string; 
5   lastName: string; 
6 }
7 
8 export const ShowUser = (props: UserViewModel) =>
9   <h1>Hallo {props.firstName} {props.lastName}!</h1>;

In TSX sind vor dem Lambda-Operator keine Umbrüche erlaubt. Schreiben Sie den Operator deshalb unbedingt ans Ende der Zeile.

Nun wird noch die Startdatei benötigt, index.tsx:

Listing: index.tsx in /src
1 import * as React from "react";
2 import * as ReactDOM from "react-dom";
3 
4 import { ShowUser } from "./components/showUser";
5 
6 ReactDOM.render(
7     <ShowUser firstName="Joerg" lastName="Krause" />,
8     document.getElementById("app")
9 );

Bleibt als letztes noch die HTML-Seite, in der die Applikation lebt. Hier werden auch die Skripte eingebunden – darunter die Ergebnisse der Übersetzung der TypeScript-Dateien. Dies unterscheidet sich nicht von den bereits gezeigten Beispielen. Die Begleitprojekte zum Buch auf Github enthalten die vollständige Lösung. Das ist die einfachste Form – eine statuslose funktionale Komponente.

11.9 Routing – Der Weg zur SPA

Das Routing in React basiert auf dem Modul react-router. Der Router verbindet lokale (clientseitige) Pfade mit Komponenten. Klickt der Benutzer auf einen entsprechend konfigurierten Link, wird die Komponente geladen. Dadurch wird bei einem Seitenwechsel nicht mehr die gesamte Seite getauscht, sondern nur ein Teil mit einer Komponente. Die Applikation selbst beleibt im Speicher und kann dadurch Zustände speichern und verhält sich wie eine klassische Desktop-Applikation.

11.9.1 Konfiguration

Da sich alles um die Routen rankt, müssen diese konfiguriert werden.

Die Konfiguration der Routen erfolgt in der Wurzelkomponente. Diese sieht dann beispielsweise folgendermaßen aus:

 1 import * as React from 'react';
 2 import * as ReactDOM from 'react-dom';
 3 import * as ReactRouter from 'react-router';
 4 
 5 var Router = ReactRouter.Router;
 6 var Route = ReactRouter.Route;
 7 var IndexRoute = ReactRouter.IndexRoute;
 8 var Link = ReactRouter.Link;
 9 
10 import * as Cmp from './components/index';
11 
12 export default class App extends React.Component<{}, void>{
13 
14   constructor() {
15     super();
16   }
17 
18   public render() {
19     return (
20       <div className="col-md-6">
21         <h1>Hallo TSX</h1>
22         <ul>
23           <li><Link to="/">Start</Link></li>
24           <li><Link to="/user">Benutzer Liste</Link></li>
25           <li><Link to="/about">Über uns</Link></li>
26         </ul>
27         <div className="content">
28           {this.props.children}
29         </div>
30       </div>);
31   }
32 
33 }
34 function render() {
35   ReactDOM.render(
36     <Router history={ReactRouter.hashHistory}>
37       <Route path="/" component={App}>
38         <IndexRoute component={Cmp.Home} />
39         <Route path="/user" component={Cmp.Users}/>
40         <Route path="/user/:id" component={Cmp.User}/>
41         <Route path="/about" component={Cmp.About} />
42       </Route>
43     </Router>
44       , document.getElementById('app')
45   );
46 }
47 render();

Der Ausgangspunkt ist die Klasse ReactRouter. Am Anfang werden ein paar Alias-Namen erstellt, damit das TSX einfacher wird. Dort stehen nun diverse Komponenten zur Verfügung, die die Konfiguration erlauben:

Der Pfad ist statisch und kann Parameter enthalten, die durch den Doppelpunkt erkennbar sind. Die Komponente, die dynamisch geladen wird, wird als DOM-Baustein angeliefert. Sie wird in this.props.children bereitgestellt. Du musst also nur diesen Aufruf irgendwo in der Vorlage platzieren und dort erscheint dann der Inhalt.

11.10 Dienste

Ein eigenes Dienstkonzept hat React nicht. Hier kommt meist jQuery zum Einsatz. In TypeScript muss dazu eine Typ-Bibliothek geladen werden:

npm install @types/jquery --save-dev

Ein typischer Dienst sieht dann folgendermaßen aus:

 1 import * as jQuery from 'jquery';
 2 
 3 import { UserViewModel } from '../viewmodels';
 4 
 5 export class UserService {
 6 
 7     public static GetAll() : JQueryPromise<Array<UserViewModel>> {
 8         return jQuery.getJSON('/users');
 9     }
10 
11 }

Interessant ist hier der Rückgabetyp JQueryPromise. Ein Promise ist ein asynchroner Aufruf, der später durch dedizierte Methoden, die ihrerseits Rückrufmethoden haben, aufgelöst wird. Zugleich wird ein Generic benutzt, sodass der Rückgabewert gleich dem ViewModel entspricht. Da die Methode static ist, wird keine Instanz benötigt.

In der Komponente, die den Dienst benutzt, sieht es dann folgendermaßen aus:

 1 import * as React from "react";
 2 import { Link } from 'react-router';
 3 
 4 import { UserViewModel } from '../../viewmodels';
 5 import { UserService } from '../../services/userService';
 6 
 7 interface UserProps {
 8     tableClasses: string
 9 }
10 
11 interface UserState {
12     users: Array<UserViewModel>
13 }
14 
15 export class ListUser
16   extends React.Component<UserProps, UserState> {
17 
18   constructor() {
19     super();
20     this.state = { users: new Array<UserViewModel>() };
21     this.loadData = this.loadData.bind(this);
22   }
23 
24   private loadData() {
25     UserService.GetAll().done((data) => this.setState({ users: data \
26 }));
27   }
28 
29   public render() {
30 
31     return (
32       <table className={this.props.tableClasses}>
33         <thead>
34             <tr>
35                 <th>Vorname</th><th>Nachname</th>
36             </tr>
37         </thead>
38         <tbody>
39           <tr>
40             <td colSpan={3}>
41                 <button onClick={this.loadData}>Lade Daten...</butto\
42 n>
43             </td>
44           </tr>
45           {
46             this.state.users.map((user) =>
47               <tr key={user.id}>
48                 <td>{user.firstName}</td>
49                 <td>{user.lastName}</td>
50                 <td>
51                     <Link to={`/user/${user.id}`}>Bearbeiten</Link>
52                 </td>
53               </tr>
54             )
55           }
56           </tbody>
57         </table>
58     );
59   }
60 
61 }

Diese Komponente zeigt eine Liste von Objekten an, die vom Server dynamisch geladen wurde. Die Besonderheiten sind hier:

11.11 Bereitstellung

Sind alle Komponenten und Dienste erstellt, entstehen aus dem TypeScript-Projekt viele *js-Dateien. Dies werden über einen dynamischen Loader oder ein Verpackungs-Werkzeug bereitgestellt. Da gibt es viel Auswahl. In diesem Buch wurde bereits WebPack vorgestellt. Für die ersten Schritte und einfaches Debuggen solltest du auch SystemJS in deinem Werkzeugkasten haben.

SystemJS verpackt selbst nicht, sondern lädt dynamisch Pakete nach. Mit dem SystemJSBuilder gibt es dafür aber auch eine Lösung.

11.11.1 Vorbereitung

Zuerst wird SystemJS installiert. Es ist wie immer über npm verfügbar.

npm install system.js --save

Beachte den Punkt im Namen, es gibt auch ein Paket ohne, was aber hier nicht gemeint ist. Ende 2017 wurde hier Version 0.20.x angeboten. Da die Bereitstellung über TypeScript auch die JSX-Transformation vornimmt, werden weitere Pakete nicht benötigt (insbesondere nicht babel-standalone) .

11.11.2 Konfiguration

Nun muss eine Konfiguration erstellt werden. Diese ist Teil der Quell-Umgebung, wird also im Ordner src platziert.

Listing: systemjs.config.js
 1 (function (global) {
 2   System.config({        
 3     paths: {
 4       'dist:': '/dist'
 5     },
 6     map: {
 7       'app': 'dist:',
 8       'react': 'dist:lib/react/react',
 9       'react-dom': 'dist:lib/react/react-dom',
10       'jquery': 'dist:lib/jquery/jquery'
11     },        
12     packages: {
13       'app': {
14           main: './index.js'
15       }
16     }
17   });
18 })(this);

Diese Datei kann sehr umfassend werden – hier wird SystemJS nur am Rande gestreift. Die wesentlichen Punkte sind:

Beim Deployment wurde festgelegt, dass die Dateien, die an den Browser ausliefert werden, in einem Ordner dist landen. Dort ist ein Ordner lib, indem die JavaScript-Bibliotheken landen. System.JS verarbeitet die von Node bekannten Modul-Ladebefehle mit require. Du findest im kompilierten Code beispielsweise folgende Aufrufe:

1 var _react = require('react');

Nun schaut SystemJS nach einem Mapping mit dem Namen react. Dort steht der Pfad ‘dist:/react/react’. dist: endet mit einem Doppelpunkt und wird deshalb als Alias interpretiert. Der aufgelöste Name ist also /dist/lib/react/react. Als Standarderweiterung wird *.js benutzt, sodass der finale Pfad /dist/lib/react/react.js ist. Dort sollte sich dann auch die entsprechende Datei befinden. Alle anderen Pfade funktionieren analog.

Das Paket selber wird in der HTML-Seite aufgerufen. Der gesamte Skript-Block am Ende sieht nun folgendermaßen aus:

1 <script src="assets/js/libs/system.js"></script>
2 <script src="assets/js/systemjs.config.js"></script>
3 <script>
4   System.import('app').catch(function (err) { console.error(err); });
5 </script>

SystemJS startet mit dem Paket ‘app’. Der Einsprungpunkt wurde auf index.js festgelegt. Diese Datei wird nur geladen und verarbeitet. Geladen wird sie aus dem Ordner dist. Der Anfang sieht etw folgendermaßen aus:

 1 "use strict";
 2 var __extends = (this && this.__extends) || function (d, b) {
 3     for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
 4     function __() { this.constructor = d; }
 5     d.prototype = b === null ? Object.create(b) : (__.prototype = b.\
 6 prototype, new __());
 7 };
 8 var React = require("react");
 9 var ReactDOM = require("react-dom");
10 var Cmp = require("./components/index");
11 var App = (function (_super) {
12     __extends(App, _super);
13     function App() {
14         return _super.call(this) || this;
15     }

Das ist übersetzter Code – du wirst so etwas nicht selbst schreiben müssen. Interessant sind nur die Aufrufe mit require.

11.12 Zusammenfassung

React ist eines der Newcomer der letzten Jahre. Mit dem Support durch Facebook hat man eine starke Umgebung. Es ist schlanker und auf den ersten Blick einfacher als Angular, dafür aber keineswegs vollständig und in einigen elementaren Fällen eher rudimentär.

Als kritisch muss man sehen, dass React von Facebook vor allem für den Eigenbedarf entwickelt wurde und die Werkzeuge und Erweiterungen stark auf den Bedarf, das Wissen und die Fähigkeiten der Entwickler bei Facebook ausgerichtet sind. Kaum ein aktuelles Projekt wird jedoch davon ausgehen, dass es 2 Milliarde Benutzer, verteilte Rechenzentren und tausende Server geben wird. So einfach React auf den ersten Blick ist, so überzogen sind einige Aspekte in Bezug auf normale Applikationen. Vieles erscheint schlicht unsinnig oder unverständlich.

Dennoch ist der schlanke Ansatz für öffentliche Websites oft besser als Angular. Applikationen im Intranet werden dagegen eher von Angular profitieren. Im Zweifelsfall hilft – wie so oft – die Erstellung eines Prototypen.