Upload
others
View
0
Download
0
Embed Size (px)
Citation preview
Web-Entwicklung mit React
Eine Einführung in die Nutzung von React
Zusammenfassung
Die von Facebook veröffentlichte Bibliothek React soll die Entwicklung von großen (Web-) Applikationen
vereinfachen, die Daten darstellen, die sich mit der Zeit ändern. Im Vordergrund steht eine möglichst
einfache und fehlerarme Implementierung der Reaktion auf Datenänderungen. Dabei verfolgt React
einen stark Komponenten-orientierten Ansatz, so dass fertige Lösungen anderer Entwickler sehr leicht in
die eigene Applikation eingebunden werden können. Dazu unterstützt React ein striktes
Programmiermodell, das die Manipulation der Darstellung einfach und robust macht.
Was ist React?
React ist eine Bibliothek für die komponentenbasierte Erstellung von dynamischen Web-Applikationen.
React wurde von Facebook für den Eigengebrauch entwickelt und der Allgemeinheit zur Verfügung
gestellt. React ist Open-Source und unterliegt der BSD-Lizenz.
Facebook schreibt dazu:
We built React to solve one problem: building large applications with data that changes over time.
Das Ziel von React ist es, die Modifikation des DOM-Baums einer Web-Seite nach der Änderung von
Daten einerseits für den Entwickler so einfach wie möglich zu machen und dabei andererseits effizient
zu bleiben, damit auch große Anwendungen möglich sind. Das Ergebnis ist eine mächtige Bibliothek, die
das Erstellen von komplexen Web-Anwendungen sehr gut unterstützt.
Der komponentenbasierte Ansatz macht es einfach, Funktionalität in Form von React-Komponenten
wiederverwendbar zu implementieren. Dementsprechend gibt es im Netz zu vielen Aufgaben bereits
fertige Komponenten, die ohne weiteres in eigenen Projekten eingesetzt werden können.
Nach der Fertigstellung der ersten Versionen erkannten die Entwickler von React, dass das Kernkonzept
der Bibliothek sich nicht nur für HTML-basierte Anwendungen eignet, sondern von der UI-Technologie
unabhängig ist. Mit Version 0.14 wurde die ursprünglich monolithische Bibliothek daher geteilt. Der
HTML- bzw. DOM-spezifische Teil (react-dom.js) wurde vom eigentlichen Kern (react.js) getrennt. Dieser
Kern ist nun auch Grundlage anderer React-Module. Facebook selbst entwickelt mit react-native eine
Unterstützung für die React-basierte Programmierung von iOS und Android unter Verwendung der
nativen Controls der jeweiligen Plattform. Daneben gibt es zum Beispiel react-canvas, das auf das
Rendern in einen HTML-Canvas spezialisiert ist. Für die Nutzung in einer Web-Applikation müssen seit
dieser Aufteilung beide genannten Javascript-Dateien in die Seiten eingebunden werden. Weiteres
Wissen um die interne Struktur ist nicht erforderlich.
Die vollständige Dokumentation zu React sowie Download-Links finden Sie unter
https://facebook.github.io/react. Dieser Artikel geht nicht auf alle Details ein, sondern vermittelt einige
Grundlagen. Wie die Überschrift bereits andeutet, wird konzentriert sich dieser Artikel auf die Nutzung
von React für die Erstellung von Web-Anwendungen.
Wie funktioniert React?
Ein "React-Programm" besteht aus einer Reihe von ineinander geschachtelten Komponenten, die in
HTML-Elemente übersetzt werden, so dass der Browser sie anzeigen kann. Diese Übersetzung von
Komponenten in HTML-Elemente erfolgt jedoch nicht direkt. React verwendet für diese Aufgabe ein
sogenanntes „virtuelles DOM”. Das ist eine leichtgewichtigere Darstellung der HTML-Elemente in Form
von JavaScript-Objekten. Das so erzeugte virtuelle DOM der HTML-Seite bzw. eines Teils davon wird
anschließend in echte HTML-Elemente umgewandelt. Diese Übersetzung von Komponenten in ein
virtuelles DOM bzw. in echtes HTML wird Rendering genannt.
Als Entwickler sieht man nicht viel von dieser Komplexität. Eine React-Komponente ist zunächst einmal
nur eine JavaScript-Klasse mit einer Methode namens render(), die HTML-Elemente ausgibt. Am
einfachsten programmiert sich eine solche Komponente nicht in purem JavaScript, sondern in einer
Erweiterung der Sprache namens JSX. Deren Syntax erlaubt im Prinzip das Einbetten von DOM-Stücken
in eine JavaScript-Datei. Eine ganz einfache Komponente sieht als JSX-Datei so aus:
var HelloWorld = React.createClass({
render: function () {
return (
<div>Hello World!</div>
);
}
});
Dieses Stückchen Code erzeugt eine Komponente namens "HelloWorld", die entweder alleine oder als
Teil von weiteren Komponenten verwendet werden kann. Wie man an dem return-Statement sieht,
erlaubt JSX anders als JavaScript das direkte Einbetten von HTML-Elementen in den Code.
Genau genommen handelt es sich bei dem div-Element im Code nicht um ein HTML-div-Element.
Vielmehr wird hier eine eingebaute Komponente von React verwendet, die 1:1 in ein div-Element
übersetzt wird. React bietet für alle HTML-Elemente (wie div, span, img) solche vordefinierten
Komponenten. Selbst dieses einfache Beispiel nutzt also bereits die Komposition von Komponenten.
Auch selbstdefinierte Komponenten wie HelloWorld können in JSX-Code in der von HTML gewöhnten
Schreibweise genutzt werden. Das sieht etwa so aus:
var HelloWorldUser = React.createClass({
render: function () {
return <HelloWorld/>;
}
});
An diesem Beispiel sieht man auch, dass die runden Klammern im Return-Statement nicht zwingend
erforderlich sind. Sie werden nur benötigt, wenn der Ausdruck mehrere Zeilen umfasst.
Ein Markup-Teil wie <HelloWorld/> ist dabei nichts anderes al ein JavaScript-Ausdruck. Dieser Ausdruck
kann mittels return von einer Funktion zurückgegeben, aber auch einer Variablen zugewiesen oder in
ein Array eingefügt werden.
Hinter den Kulissen werden JSX-Dateien in ganz konventionelles JavaScript übersetzt. Die beiden obigen
Snippets sehen nach der Übersetzung dann etwa so aus:
var HelloWorld = React.createClass({
displayName: "HelloWorld",
render: function render() {
return React.createElement(
"div",
null,
"Hello World!"
);
}
});
var HelloWorldUser = React.createClass({
displayName: "HelloWorldUser",
render: function render() {
return React.createElement(HelloWorld, null);
}
});
Diesen Code führt der Browser dann mittels seiner eingebauten JavaScript-Engine aus. Als Entwickler
kann man auch diese reine JavaScript-Form natürlich auch für die Entwicklung nutzen. Für JSX spricht
jedoch die deutlich bessere Lesbarkeit!
Diese Übersetzung in pures JavaScript kann entweder als Teil des Build-Prozesses, bei der Auslieferung
durch den Web-Server oder sogar erst im Client geschehen, wobei letztere Variante aus Performanz-
Gründen nur für Entwicklungsversionen zu empfehlen ist.
Um das von einer React-Komponente generierte HTML zu sehen zu bekommen, muss man sie in ein
Container-Element rendern. Das ist meist ein div-Element irgendwo auf der HTML-Seite mit einer ID:
<div id="container"/>
Um unsere HelloWorld-Komponente in dieses Element zu rendern, schreibt man anschließend (wieder
in JSX-Syntax):
ReactDOM.render(<HelloWorld/>, document.getElementById("container"));
Wenn dieser Code ausgeführt wird, wird der bisherige Inhalt des Zielelements entfernt und durch das
Resultat der render-Methode der Komponente ersetzt.
Das Ergebnis dieses Vorgangs ist in diesem Fall eine HTML-Seite mit folgendem Inhalt:
<div id="container"><div>Hello World!</div></div>
Die Nutzung von React erfordert in der jeweiligen Web-Site lediglich die Einbettung zweier Skripte.
Diese können z. B. von cloudflare bezogen werden:
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-with-
addons.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.3/react-
dom.min.js"></script>
React selbst hat keine weiteren Abhängigkeiten.
React und .NET
Die Verwendung von React ist selbstverständlich mit allen Technologien zur Erstellung von Web-
Anwendungen nutzbar.
Alle in diesem Text erwähnten Beispiele sind als vollständige .NET Solutions verfügbar und können etwa
mit der Community-Edition von Visual Studio 2015 (oder neuer) ausprobiert werden. Die Projekte finden
Sie unter https://github.com/s-buss/ReactIntro.
Will man React in ASP .NET MVC nutzen, empfiehlt es sich je nach genutzter MVC-Version entweder das
NuGet-Paket React.Web.Mvc4 (für MVC 4 oder 5) oder React.Web.AspNet (ab MVC vNext bzw. 6)
einzubinden. Diese Pakete bieten eine On-The-Fly-Übersetzung von JSX-Dateien bei der Auslieferung an
die Clients an, so dass ein expliziter Build-Step zur Übersetzung nicht erforderlich ist. Außerdem
erlauben diese Pakete auch das Server-seitige Rendern von React-Komponenten. Das bedeutet, dass der
Server bereits das erzeugte HTML ausliefert. Das kann Vorteile bei der Geschwindigkeit des
Seitenaufbaus und für die Sichtbarkeit in Suchmaschinen bedeuten. Für Details sei auf die
Dokumentation von React und den genannten NuGet-Paketen verwiesen.
Komponenten mit Properties
Die Komponente aus dem vorherigen Abschnitt ist ziemlich uninteressant, da sie komplett statisch ist. Es
gibt keine Möglichkeit, Daten in die Komponente hineinzugeben, um die Ausgabe zu beeinflussen. Das
ändert sich durch Properties.
Jeder React-Komponente kann man bei der Verwendung beliebige sogenannte Properties mitgeben. Die
Werte der Properties sind einfache Werte wie Strings bzw. Zahlen oder beliebige JavaScript-Objekte
(Strukturen, Arrays, ...). In JSX werden Property-Werte syntaktisch wie HTML-Attribute übergeben.
Intern werden die Property-Werte in einem Feld namens props gespeichert. Auf die Properties einer
Komponente greift man deshalb innerhalb der render-Methode über this.props.propertyName zu.
Mit Properties sieht das Beispiel aus dem vorherigen Abschnitt vielleicht so aus:
var HelloWorld = React.createClass({
render: function() {
return (
<div className="hello" style={{ fontSize: "20px" }}>
Hello {this.props.name}!
</div>
);
}
});
ReactDOM.render(<HelloWorld name="John"/>,
document.getElementById("react-container"));
Hier ist zu erkennen, wie man Properties an eigene Komponenten wie HelloWorld übergibt. Unsere
HelloWorld-Komponente besitzt jetzt ein Property name, dem ein Wert zugewiesen kann bzw. muss,
wenn diese Komponente benutzt wird. Der Name wird in das erzeugte div-Element eingesetzt. Die
geschweiften Klammern erlauben die Einbettung beliebiger JavaScript-Ausdrücke in den Markup
innerhalb des return-Statements.
In diesem Beispiel gibt es keine Deklaration der Properties der HelloWorld-Komponente. Die
Komponente erwartet einfach, dass bei der Verwendung ein String als Wert für das Property name
übergeben wird. Optional kann man aber auch in der Komponente deklarieren, welche Properties es
gibt und welche Typen sie haben. Der JSX-Compiler prüft dann die korrekte Verwendung der
Komponente zur Compile-Zeit. Wie das geht, ist unter
https://facebook.github.io/react/docs/reusable-components.html#prop-validation
zu sehen.
Ebenfalls kann man sehen, dass auch die vordefinierten Komponenten für HTML-Elemente wie <div>
Properties akzeptieren, die im Wesentlichen in HTML-Attribute umgewandelt werden. Dabei wird noch
einmal deutlich, dass es hier nicht um die „echten“ HTML-Elemente geht, sondern um React-
Komponenten gleichen Namens. In diesem Beispiel sind zwei Unterschiede sichtbar:
1. Anstelle des HTML-Attribut class verwendet man das Attribut className.
2. Das Property styles nimmt als Wert keinen String, sondern ein JavaScript-Objekt mit den
einzelnen CSS-Attributen als Eigenschaften. CSS-Attribute, die Minuszeichen enthalten wie font-
size werden dabei zudem über Eigenschaftsnamen in Camel-Case-Schreibweise angesprochen, z.
B. fontSize. Die doppelten geschweiften Klammern kommen von der Einbettung eines
JavaScript-Ausdrucks (das äußere Klammerpaar) in das Markup. Das innere Klammerpaar gehört
zum JavaScript-Objekt.
Der Code in der Render-Methode ist ganz normales JavaScript. Die einzige Erweiterung ist die HTML-
ähnliche Syntax für die Verwendung von Komponenten! Man kann deshalb Property-Werte und Inhalte
von Unterkomponenten auch in Variablen speichern und in das Ergebnis einsetzen. Das könnte
folgendermaßen aussehen:
var HelloWorld = React.createClass({
render: function() {
var c = "hello";
var s = {
fontSize: "20px"
};
var name = this.props.name;
return (
<div className={c} style={s}>Hello {name}!</div>
);
}
});
Die "Berechnung" der Werte darf natürlich beliebig kompliziert sein und die Variablennamen können
sprechender sein!
Properties sind aus Sicht der Komponente strikt schreibgeschützt. Eine Komponente sollte niemals
Werte in seinem props-Objekt ändern!
Komponentenzustand und Ereignisbehandlung
Neben den Properties kennt jede React-Komponente auch einen Zustand (state). Anders als ihre
Properties darf und soll eine Komponente ihren Zustand ändern! In der Regel passiert das aufgrund von
Ereignissen, meist durch Nutzeraktionen. Ähnlich wie die Properties wird der Zustand einer Komponente
in einem JavaScript-Objekt abgelegt, in diesem Fall this.state.
Bei Zustandsänderungen kommt ein wichtiger Teil der React-Architektur zum Tragen. Ist eine
Komponente einmal gerendert, gibt es keine Möglichkeit, um das generierte virtuelle DOM explizit zu
verändern (z. B. neue Elemente hinzuzufügen oder vorhandene zu verändern). Den einzigen Weg, den
React als Reaktion auf Zustandsänderungen kennt, ist das erneute Rendern der Komponente(n). Das
klingt zunächst ineffizient, macht die Programmierung einer aktiven Komponente aber tatsächlich
extrem einfach.
Will man unsere HelloWorld-Komponente um einen Mouse-Over-Effekt erweitern, sieht das zum
Beispiel so aus:
var HelloWorld = React.createClass({
getInitialState: function() {
return { isHovered: false, dummy: 17 };
},
handleMouseEnter: function() {
this.setState({ isHovered: true });
},
handleMouseLeave: function () {
this.setState({ isHovered: false });
},
render: function () {
var className = "hello";
var additionalText = undefined;
if (this.state.isHovered) {
className = "hello hovered";
additionalText = <span> (hovered)</span>;
}
return (
<div className={className} style={{ fontSize: "20px" }}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}>
Hello {this.props.name}! {additionalText}
</div>
);
}
});
Die Methode getInitialState ist von React dafür vorgesehen, das Feld state einmalig zu füllen. Sie wird
vor dem ersten Rendern aufgerufen (sofern sie in der Komponentenklasse definiert wurde) und der
Rückgabewert wird in this.state gespeichert.
handleMouseEnter und handleMouseLeave sind Event-Handler für die gleichnamigen Maus-Ereignisse.
Alles was sie tun, ist den Zustand der Komponente über die Methode setState zu modifizieren. Zu
beachten ist hier, dass die Methode den Zustand nicht komplett ersetzt, sondern nur die angegebenen
Werte überschreibt und alle anderen unverändert lässt. Der Wert von dummy bleibt in diesem Beispiel
also erhalten.
Die Methode render wurde gegenüber dem vorgehenden Beispiel so modifiziert, dass sie abhängig vom
Zustand isHovered unterschiedliche Klassen für das div-Element verwendet. Mit einem geeigneten
Stylesheet ändert das möglicherweise die Hintergrundfarbe.
Außerdem wird im Fall isHovered ein zusätzlicher Text in dem div-Element hinter der Begrüßung
dargestellt. Die Variable additionalText ist ein Beispiel dafür, wie Unterkomponenten optional
verwendet werden. Im Fall isHovered = false bleibt der Wert undefined und die Einbettung
{additionalText} wird ignoriert. Andernfalls enthält diese Variable eine Instanz der (eingebauten)
Komponente span, die in das von HelloWorld gerenderte HTML eingebettet wird.
Was passiert zur Laufzeit?
Zu Beginn wird die Komponente im Zustand isHovered = false gerendert. Sobald der Mauszeiger den
Bereich der Komponente betritt, wird die entsprechende Handler-Methode handleMouseEnter
aufgerufen. Das führt dazu, dass der Zustand auf isHovered = true geändert wird. Danach wird die
Komponente erneut gerendert und generiert dieses Mal ein anderes Markup. Umgekehrt passiert das
gleiche.
Nach dem jeweiligen Neurendern der Komponente aktualisiert React automatisch die HTML-Darstellung
der Komponente. Dabei stellt React fest, welche Teile des physikalischen DOM-Baums entfernt oder
verändert wurden oder hinzugekommen sind und nimmt die entsprechenden Veränderungen vor.
Dieser Vorgang heißt Reconciliation. Nach dem Abschluss der Reconciliation zeigt der Browser die
aktualisierte Seite.
Bei größeren Anwendungen mit großen, tief geschachtelten Komponenten können tatsächlich beide
Schritte (Rendering und Reconciliation) Performanz-kritisch sein. Für beide möglichen Engpässe bietet
React aber Mittel an, um die Performanz-Einbußen zu minimieren. In den folgenden Abschnitten
werden zwei grundlegende Techniken verwendet: das Verhindern unnötiger Renderings
(shouldComponentUpdate bzw. PureRenderMixin) sowie das Minimieren des Reconciliation-Aufwands
(durch das Property key).
Das Ergebnis ist eine wirklich leicht zu beherrschende Technik für die Erstellung von sehr
leistungsfähigen Web-Anwendungen.
Ein größeres Beispiel
Auf GitHub finden Sie ein größeres Beispiel namens „04 – MovieList“. Dieses zeigt, wie eine etwas
komplexere Komponente aussieht. In diesem Beispiel geht es um die Liste aller 24 bislang erschienenen
offiziellen Bond-Filme. Zu allen Filmen liegen einige Daten in Form einer Liste von JavaScript-Objekten
vor.
Pro Film gibt es einen Eintrag mit Titel Erscheinungsjahr, dem Namen des Bond-Darstellers, einem
Verweis auf eine Bilddatei mit dem Filmposter des Films und einer kurze Handlungsübersicht1. Diese
Informationen sollen auf ansprechende Weise als Web-Seite dargestellt werden. Der Eintrag für den
ersten Film sieht wie folgt aus:
{
title: "Dr. No",
year: 1962,
bond: "Sean Connery",
poster: "http://image.tmdb.org/t/p/w90/utXgLvPouQaQj8qmJ0sUtxCacsi.jpg",
overview: "John Strangways, […]"
}
Der Einfachheit halber wird diese Liste im Beispiel nicht als JSON-Datei geladen, wie es wahrscheinlich in
einer echten Anwendung der Fall wäre. Stattdessen ist sie direkt als JavaScript-Datei (Scripts/Data.js) in
die Seite eingebunden.
In der Datei Scripts/BondMovieList.jsx finden Sie die Definition von zwei React-Komponenten. Die
Äußere von beiden (BondMovieList) stellt eine Liste von Filminfos dar. Sie verwendet eine innere
Komponente (BondMovie), die einen einzelnen Film darstellt. Das ist schon alles.
Die Komponente BondMovie hat zwei Properties: index gibt den Index des Films in der Liste aller Filme
an, movie enthält die Informationen über den darzustellenden Film. Die Komponente besteht eigentlich
nur aus einem Stück Markup, das in Zusammenarbeit mit einem geeigneten Style-Sheet eine Ausgabe
erzeugt, die etwa so aussieht:
1 Die Daten wurden aus Wikipedia (https://en.wikipedia.org/wiki/List_of_James_Bond_films) entnommen. Die
Bilder für die Poster stammen aus der TMDb (https://www.themoviedb.org).
Etwas interessanter ist die Komponente BondMovieList:
var BondMovieList = React.createClass({
render: function () {
var movieList = this.props.movies;
var movieElems = movieList.map(function (movie, index) {
var key = movie.year;
return (
<BondMovie key={key} index={index} movie={movie}/>
);
});
return (
<div className="bond-movielist">{movieElems}</div>
);
}
});
Diese Komponente bekommt als Property movies eine ganze Liste von Filmeinträgen. Diese Liste wird in
eine Liste movieElems von Instanzen der Komponente BondMovie umgewandelt, wobei jeweils der
Listenindex und der einzelne Film wiederum als Properties übergeben werden.
Das Ergebnis ist schließlich ein div-Element, das die von BondMovie gerenderten einzelnen Filme
umschließt.
An dieser Stelle sind zwei Dinge interessant:
Erstens sei darauf hingewiesen, dass eine Komponente immer nur exakt ein Element zurückgeben darf.
Das ist eine Einschränkung von React. Ein einfaches return movieElems; würde nicht funktionieren, das
umschließende div ist notwendig.
Das zweite interessante Detail ist das zusätzliche Property key, das jedem BondMovie mitgegeben wird.
Dieses Property kann jeder Komponente übergeben werden und ist bei der Darstellung von Listen
wichtig. Die Vorgabe ist, jedem Element einen Schlüssel zuzuweisen, der das jeweilige Element eindeutig
(innerhalb der Liste bzw. seiner Vaterkomponente) identifiziert. Im Beispiel machen wir uns zunutze,
dass pro Jahr maximal ein Bond-Film erscheint und es deshalb als Schlüssel taugt.
Diese Zuordnung von eindeutigen Bezeichnern hilft React bei der Optimierung des Rendering-Prozesses,
genauer der Reconciliation. Es erlaubt, bei Aktualisierungen die Elemente der neuen Liste denen der
alten Liste zuzuordnen. Dadurch ist es möglich, Verschiebungen von Elementen der Liste zu erkennen
und das Rendering auf dazukommende Elemente zu beschränken. Mehr dazu folgt im nächsten
Abschnitt. Ohne diese Schlüssel ist das erneute Rendern von längeren Listen sehr ineffizient, da immer
die komplette Liste gerendert werden muss, auch wenn nur ein einziges Element hinzugekommen ist
oder entfernt wurde. React gibt deshalb eine Warnung auf der JavaScript-Konsole aus, wenn Listen ohne
key-Properties gerendert werden.
Mehr Details zu diesem Thema finden Sie unter
https://facebook.github.io/react/docs/reconciliation.html
Hinzufügen von Nutzerinteraktion
Eine etwas erweiterte Variante des letzten Beispiels ist „05 - SortableMovieList“ (ebenfalls unter dem
oben genannten GitHub-Link zu finden). Wie der Name schon andeutet, wird der Code hier um die
Möglichkeit erweitert, die Liste umzusortieren.
Hier kommen Zustand und Ereignisbehandlung aus dem Abschnitt „ Komponentenzustand und Ereignisbehandlung“ zum Einsatz. Dieses Beispiel ist aber etwas realistischer.
Wieder werden die Ereignisse MouseEnter und MouseLeave dazu verwendet, die Hintergrundfarbe des
Elements zu verändern und zusätzliche Elemente einzublenden. In diesem Fall sind diese zusätzlichen
Elemente zwei Buttons, mit denen der Nutzer den Film in der Liste auf und ab bewegen kann.
Mit den notwendigen Erweiterungen werden beide Komponenten etwas länglich, so dass ab jetzt nur
noch die interessanten Ausschnitte dargestellt werden.
Der erste Unterschied zum statischen Beispiel ist dass die Liste ab jetzt veränderlich ist, also zum
Zustand der Komponente BondMovieList gehört. Von außen wird nur noch die initiale Liste bzw.
Sortierung als Property initialMovies übergeben. Die Methode getInitialState überträgt den Property-
Wert in den Zustand unter dem Namen movies:
getInitialState: function() {
return { movies: this.props.initialMovies };
}
Die React-Dokumentation weist eindrücklich darauf hin, dass die Duplizierung von Properties in den
State ein Anti-Pattern ist und ein Entwickler beides strikt voneinander trennen sollte. Die Initialisierung
der Zustands-Eigenschaften aus Properties ist aber explizit erlaubt. Wichtig ist, dass in der Komponente
immer nur die Liste aus dem state verwendet wird. Das sieht man z. B. in der geänderten render-
Methode. Dort steht jetzt:
[…]
var movieList = this.state.movies;
[…]
Diese Änderung reicht schon aus, um die Liste editierbar zu machen. Ein bisschen wird die
Implementierung der Umsortierung dadurch erschwert, dass die aktiven Elemente Buttons in der
Komponente BondMovie sind, ihre Verwendung aber eine Aktualisierung der umgebenden
BondMovieList sind. Von BondMovie aus besteht aber kein direkter Zugriff auf die Vaterkomponente.
Deshalb ist die Verwendung eines Callbacks notwendig.
Die eigentliche Manipulation der Liste ist als Methode in BondMovieList implementiert:
moveMovie: function (oldIndex, newIndex) {
if (newIndex < 0 || newIndex >= this.state.movies.length) {
return;
}
var newList = this.state.movies.slice(0);
var tmp = newList[newIndex];
newList[newIndex] = newList[oldIndex];
newList[oldIndex] = tmp;
this.setState({ movies: newList });
}
Diese Methode verschiebt einen Film vom alten Platz oldIndex auf den neuen Platz newIndex, indem es
ihn mit dem Eintrag an der neuen Position vertauscht. Am Ende steht wieder ein Aufruf von setState,
der die neue Liste in den Zustand übernimmt und eine Aktualisierung durch erneutes Rendern auslöst.
Die beiden Richtungs-Buttons, die beim Mouse-Over über einen Film sichtbar werden, sollen jetzt die
Liste entsprechend manipulieren. Dazu übergibt BondMovieList ihre Methode als Property an
BondMovie:
<BondMovie key={key} index={index} movie={movie} onMove={component.moveMovie}/>
BondMovie registriert wiederum Event-Handler für das Click-Event seiner beiden Buttons. Diese Event-
Handler können auf das Property index zugreifen und die übergebene Methode mit entsprechenden
Parametern aufrufen.
moveUp: function () {
[…]
// Let the parent move this entry to a new position.
this.props.onMove(this.props.index, this.props.index - 1);
},
[…]
render: function () {
[…]
var buttonUp = undefined;
var buttonDown = undefined;
if (this.state.isHovered) {
[…]
buttonUp = <div […] onClick={this.moveUp}>[…]</div>;
buttonDown = <div […] onClick={this.moveDown}>[…]</div>;
}
return […];
}
Das Ergebnis dieser Bemühungen ist, dass tatsächlich ein Klick auf einen der erscheinenden Buttons den
Film um eine Position nach oben oder unten verschiebt.
Technisch funktioniert das ganze wie gesagt dadurch, dass die Komponente BondMovieList ihren
Zustand ändert und die komplette Film-Liste neu rendert. Dabei wird für jedes BondMovie die render-
Methode aufgerufen, auch wenn sich an der Darstellung dieses Elements überhaupt nichts geändert hat.
Diese unnötigen Aufrufe von render können bei größeren Projekten lange dauern.
React bietet hier eine Eingriffsmöglichkeit zur Vermeidung unnötiger Render-Aufrufe. Eine Komponente
kann eine Methode namens shouldComponentUpdate (siehe hier) implementieren. Diese wird – sofern
vorhanden – vor jedem Aufruf von render aufgerufen. Falls sie false zurückgibt, verzichtet React auf das
Rendering der Komponente und geht davon aus, das letzte Ergebnis von render noch gültig ist. Wir
könnten hier zum Beispiel prüfen, ob der Film und sein Index unverändert sind. Übergeben werden das
neue props-Objekt und das neue state-Objekt, die mit den bisherigen Werten (this.props bzw. this.state)
verglichen werden können. Noch komfortabler geht das jedoch über ein Mixin. Das ist eine in
verschiedenen Komponenten wiederverwendbare Implementierung von Methoden.
Zur Vermeidung von unnötigen Renderings gibt es ein Add-On namens PureRenderMixin, das uns die
Arbeit des Vergleichs abnimmt und eine generische Implementierung für shouldComponentUpdate
liefert. Die Verwendung ist denkbar einfach:
var BondMovie = React.createClass({
mixins: [ React.addons.PureRenderMixin ],
[…]
};
Den Effekt kann man sehen, wenn man in der Beispiel-Solution die Konsole im Browser betrachtet. In
der render-Methode steckt eine einfache console.log-Anweisung, die bei jedem Aufruf der Methode
eine Zeile auf die Konsole schreibt. Ohne das Mixin wird die Methode bei jedem Verschieben eines Films
24-mal aufgerufen, da jeder Film der Liste neu gerendert wird. Mit dem Mixin passiert das nur noch 2-
mal, da die beiden vertauschten Filme ihren Index ändern. Für alle anderen Filme wird das Rendern
unterdrückt. Genau genommen kommen jeweils noch 4 Aufrufe von render dazu. Schuld daran ist das
Mouse-Over.
In der Methode shouldComponentUpdate liegt die Lösung vieler Performanz-Probleme von React-
Anwendungen. Schafft man es, unnötige render-Aufrufe zu vermeiden, ist React trotz des scheinbaren
Brute-Force-Ansatzes auch bei größeren Komponenten und Datenmengen sehr schnell.
Fazit
React kann die Behauptung vom Anfang des Textes tatsächlich einlösen. Das Programmiermodell von
React vereinfacht kompliziertere Manipulationen am DOM-Baum einer Seite, da sich für den
Programmierer jede Änderung nur als erneutes Rendern einer Komponente darstellt. Das verhindert die
Entstehung von komplexem Code für Manipulationen der Seite und reduziert die Wahrscheinlichkeit
von Fehlern. Mit den richtigen Hilfestellungen bleibt die Performanz dabei nicht auf der Strecke.