React ist eine OpenSource View Rendering Library von Facebook. Es gibt bereits viele Frontend-Libraries. Warum entwickelt Facebook eine eigene? Die Motivation ist, UI-Komponenten besser (vorhersehbarer) entwickeln zu können. Der Ansatz, um Komponenten vorhersehbarer zu entwickeln, ist es, eine Hierarchie von modularisierten Komponenten zu etablieren. Letztere sind „DOM“-Elemente, die die Struktur sowie alle unterstützenden Daten und Methoden beschreiben.
Auf der React-Site gibt es ein „Starter Kit“, um React auszuprobieren. Im produktiven Einsatz wird man dieses sicher nicht verwenden, aber um diesem Blog-Beitrag zu folgen, ist es sicherlich hilfreich. Die ersten Schritte in React gehen wir innerhalb der Code-Basis des Starter Kits.
Komponenten
Wie sieht nun eine React-Komponente aus? Dafür habe ich im Starter Kit einen neuen Ordner src
und dort eine index.html
-Datei angelegt.
[code language=“html“]
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="../build/react.js"></script>
<script src="../build/react-dom.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
</head>
<body>
<div id="app"></div>
<script type="text/babel">
var HelloWorld = React.createClass({
render : function(){
return (
<h1>Hello world</h1>
)
}
});
ReactDOM.render(
<HelloWorld></HelloWorld>,
document.getElementById(‚app‘)
);
</script>
</body>
</html>
[/code]
Im head-Tag laden wir die React- und die React-DOM-Library. Zudem fügen wir Babel als Transpiler ein, um ES2015 (JavaScript mit den neuesten, bleeding-edge Features) in Browser-verträgliches JavaScript (meist ES5) zu wandeln. Natürlich würde man in einer produktiven Umgebung niemals den Transpile-Schritt im Browser durchführen, doch für erste Schritte ist dies am einfachsten.
Eine Komponente sollte irgendwo im DOM gerendert werden. Daher gibt es einen leeren div-Tag mit der id „app“.
[code language=“html“]
<div id="app"></div>
[/code]
Für das Rendering im DOM ist die „react-dom“-Library zuständig. Wie man sieht, ist React selbst also nicht direkt an das DOM gekoppelt. Es ließen sich auch andere Libaries einbinden, wenn React nicht ins DOM rendern soll, aber dies ist sicher Thema eines weiteren Blog-Beitrags.
Die render
-Methode von ReactDOM hat zwei Parameter: die Komponente, die gerendert werden soll, und den Ort, an dem die Komponente gerendert werden soll.
[code language=“javascript“]
ReactDOM.render(
<HelloWorld></HelloWorld>,
document.getElementById(‚app‘)
);
[/code]
Der zweite Parameter der ReactDOM.render()
-Methode sagt der Methode, wo ReactDOM rendern soll. Dies ist ein gewöhnlicher ID-Selektor. Irritierend an dieser ReactDOM.render()
-Methode ist ihr erster Parameter. Scheinbar kann man direkt HTML in einer JavaScript-Funktion verwenden, doch ist dies nicht wirklich HTML, sondern JSX. JSX ist eine Syntax-Erweiterung für React, die aussieht wie beliebiges Markup. Babel ist dafür zuständig, JXS nach JavaScript zu kompilieren. Aus <HelloWorld></HelloWorld>
macht Babel in etwa React.createElement(HelloWorld)
.
Schauen wir uns nun unsere <HelloWorld>
-Komponente an.
React unterstützt bereits ES2015-Klassen als Komponenten. In diesem einfachen Beispiel verwenden wir jedoch noch die createClass
-Methode, um eine Komponenten-Klasse zu erzeugen. Eine React-Komponente hat eine render
-Methode (nicht zu verwechseln mit der render
-Methode von ReactDOM!), die die Komponente rendert. Diese render
-Methode kann beliebige JSX-Elemente zurückgeben. Hier geben wir eine Überschrift mit dem Text „Hello world“ aus.
[code language=“javascript“]
var HelloWorld = React.createClass({
render : function(){
return (
<h1>Hello world</h1>
)
}
});
[/code]
Das Ganze ist noch nicht wirklich spannend und erklärt auch nicht, warum Komponenten auf diese Weise vorhersehbarer entwickelt werden können. Was bietet React, um Komponenten wirklich vorhersehbar zu entwickeln? React kennt ‚Status‘ und ‚Properties‘.
Status: Alle Daten, die notwendig sind, um eine Komponente anzuzeigen und das Verhalten zu bestimmen (ähnlich ViewModel).
Properties: Daten, die eine Kind-Komponente von ihrer Eltern-Komponente hineingereicht bekommen.
Properties
Bevor wir uns den Status einer Komponente anschauen, sehen wir uns zunächst die Properties (oder props
in React) an. Wir können einer Komponente Properties hinzufügen und so Informationen in unsere Komponente hineinreichen.
[code language=“javascript“]
var Hello = React.createClass({
render : function(){
return (
<h1>Hello {this.props.name}</h1>
)
}
});
ReactDOM.render(
<Hello name="Peter"></Hello>,
document.getElementById(‚app‘)
);
[/code]
Unsere Property heißt name
. Properties werden erzeugt, wenn unsere Komponente initialisiert oder gerendert wird. Dies unterscheidet Properties vom Zustand (state
) einer Komponente, die wir später behandeln. Den Inhalt der Property name
rendern wir nun in unserer Überschrift. Dazu verwenden wir geschweifte Klammern. Wichtig ist, dass der Inhalt der geschweiften Klammern JavaScript ist. D.h., man kann nicht nur vorberechnete Werte ausgeben, sondern auch selbst Berechnungen durchführen (z.B. {this.props.name + 'foo'}
).
Hilfreich ist, dass man Default-Properties definieren kann.
[code language=“javascript“]
var Hello = React.createClass({
getDefaultProps : function(){
return {
name : "World"
}
},
render : function(){
return (
<h1>Hello {this.props.name}</h1>
)
}
});
ReactDOM.render(
<Hello></Hello>,
document.getElementById(‚app‘)
);
[/code]
Dazu legen wir in unserer Komponente die Methode getDefaultProps
an. Diese wird von React aufgerufen, bevor die Komponente gerendert wird. Auch wenn wir die Komponente ohne die Property name
aufrufen, gibt sie trotzdem noch „Hello world“ aus. Es empfiehlt sich – soweit möglich – Default-Properties zu definieren, denn dies macht die Verwendung einer Komponente schlanker.
Um eine saubere Schnittstelle zu schaffen, ist es empfehlenswert, Properties, ihre Default-Werte und deren Typen zu definieren. Typen sind – auch in einer dynamisch typisierten Sprache wie JavaScript – sinnvoll.
[code language=“javascript“]
var Hello = React.createClass({
getDefaultProps : function(){
return {
name : "World"
}
},
propTypes : {
name : React.PropTypes.string
},
render : function(){
return (
<h1>Hello {this.props.name}</h1>
)
}
});
ReactDOM.render(
<Hello name={23}></Hello>,
document.getElementById(‚app‘)
);
[/code]
Da hier ein Typ string
definiert wurde, bekommt man eine sprechende Fehlermeldung, wenn man einen falschen Typ (wie number
) übergibt: ''Warning: Failed propType: Invalid prop `name` of type `number` supplied to `HelloWorld`, expected `string`.''
State
Neben Properties hat der Zustand (state
) einer Komponente Einfluss darauf, wie sie sich rendert. Allerdings kann man einen Zustand nicht in eine Komponente hineingeben (dafür sind Properties zuständig). Properties werden erzeugt, wenn eine Komponente initialisiert wird. Der Status einer Komponente existiert nur innerhalb einer Komponente.
[code language=“javascript“]
var Hello = React.createClass({
getDefaultProps : function(){
return {
name : "World"
}
},
propTypes : {
name : React.PropTypes.string
},
getInitialState : function(){
return {
value : ‚init‘
}
},
render : function(){
return (
<h1>Hello {this.props.name} – {this.state.value}</h1>
)
}
});
ReactDOM.render(
<Hello name="World"></Hello>,
document.getElementById(‚app‘)
);
[/code]
Ähnlich wie die getDefaultProps
-Methode gibt es eine getInitialState
-Methode, die ein Objekt zurückgibt. Auf dieses Objekt können wir – z.B. in der render
-Methode – über this.state....
zugreifen.
Eine React-Komponente hat einen Lebenszyklus. Ein wichtiger Schritt im Lebenszyklus ist, wenn die Komponente eingeladen (mounted) wird: componentDidMount
. Wenn dies eintritt, kann eine (optionale) Callback-Funktion definiert werden, die ausgeführt wird, nachdem die Komponente eingeladen wurde.
[code language=“javascript“]
var Hello = React.createClass({
getDefaultProps : function(){
return {
name : "World"
}
},
propTypes : {
name : React.PropTypes.string
},
getInitialState : function(){
return {
value : ‚init‘
}
},
componentDidMount : function(){
this.setState({
value : this.state.value + ‚ – component did mount‘
})
},
render : function(){
return (
<h1>Hello {this.props.name} – {this.state.value}</h1>
)
}
});
ReactDOM.render(
<Hello name="World"></Hello>,
document.getElementById(‚app‘)
);
[/code]
In der getInitialState
-Methode wurde der initiale State von value
auf „init“ gesetzt. Sobald die Komponente geladen wurde, wird der State verändert ('init' + '- component did mount'
). Der State wird nicht direkt, sondern über die setState
-Methode verändert. Es gibt keine vorgegebene Möglichkeit, um die Typen eines Zustands zu definieren, da der Zustand nur innerhalb einer Komponente verwendet wird und keine Schnittstelle nach außen darstellt (natürlich steht es jedem frei, TypeScript, Google Closure Annotations oder Facebook Flow für zusätzliche Typisierung zu verwenden).
Wenn man State in einer Komponente verwendet, dann ist sie natürlich nicht mehr seiteneffektfrei (pure functional).
Dies war nun das „Hello world“ in React. Was macht aber nun React so besonders? Das ist das Thema des nächsten Blog-Beitrags.