Eine Blog-Serie zum Vergleich von Elm, ReactJS und AngularJS anhand eines praktischen Beispiels.Hier geht es direkt zu [Teil 1, 2, 4, 5, 6]
Der dritte Reise-Teilnehmer: ReactJS mit TypeScript
React kann es kaum erwarten, den anderen von seinem neuen Release 15 zu erzählen. Endlich eine richtige Nummer! Elm lugt etwas neidisch von seinem Smartphone hoch.
Von den Dreien war React sicher der Kurligste. Klar, keiner von ihnen war ohne Ecken, aber React drückte sich stets in einem lustigen Dialekt aus, der sich zwar teilweise wie HTML anhörte, aber so richtig verstehen konnte ihn trotzdem niemand (JSX Syntax).
Ausser dieser einen, von der React so schwärmte: Babel. Die hatte ihn schon früh gut verstanden und mittlerweile ist aus der anfänglichen Romanze eine richtig Beziehung geworden.
ReactJS wurde entwickelt von Facebook und Instagram und stellt momentan die deutlichste Konkurrenz zu Google’s Angular dar. React ist zwar eine Bibliothek und Angular ein Framework, aber wir interessieren uns nur dafür, wie leicht damit Web-Anwendungen entwickelt werden können.
Architektonisch gesehen stellt wohl die Verwendung des virtuellen DOM (Document Object Model), und den damit verbundenen Konsequenzen den grössten Unterschied dar.
Ein Virtual DOM, also eine In-Memory-Darstellung des echten HTML-DOMs, bringt bei gleichzeitiger Vereinfachung der Programmierung einen enormen Geschwindigkeitsvorteil. Bei jeder Änderung wird das Framework angewiesen, alles neu zu zeichnen. Das klingt zwar ineffizient – und wäre es auch, würde man den DOM direkt bearbeiten – jedoch durch die Strukturierung der Applikation in möglichst viele Komponenten mit unveränderbarem Zustand (also Komponenenten, die von aussen mit Eigenschaften versehen werden, die sich nicht mehr ändern), kann das Framework effizient berechnen, welche tatsächlichen Änderungen sich durch eine Aktion im DOM ergeben würden und nur diese anwenden.
Diese Unveränderbarkeit (eben: immutability) führt dazu, dass das Framework dem Programmierer einen funktionalen Stil geradezu aufdrängt, weil es sich halt eben dafür eignet.
Gegenüber Angular fühlt sich ReactJS modularer an, sicher nicht zuletzt durch die HTML-ähnliche Tag-Syntax namens JSX, die innerhalb von Komponenten verwendet werden kann, um die View zu beschreiben und zu binden.
Ein Argument gegen ReactJS war in den Anfängen, dass dadurch ein zusätzlicher Kompiler (oder Transpiler) benötigt würde, um JSX nach javascript zu übersetzen.
Das Argument gilt aber nicht nur für JSX, sondern auch für jede andere Geschmacksrichtung von Javascript, sei es CoffeeScript, TypeScript oder ES6-nach-ES5-Übersetzung.
BabelJS ist ein derartiger Javascript Compiler, der möglichst viele verschiedene Formate und Sprachen nach Javascript übersetzen will (auch z.B. Stylesheets und Fonts), was Modularisierung erheblich vereinfacht.
Dieser “One-Compiler-To-Bind-Them-All” hat im Verlaufe seines Feldzuges zur Markt-Dominanz nebenbei einen JSX-Loader entwickelt und angeboten, welcher es geschafft hat, den Transpiler von Facebook zu verdrängen. Dieser hiess übrigens JSTransform und wird nicht mehr unterstützt. JSX-Fragmente werden heutzutage mit Babel übersetzt. (eine Abhängigkeit und ein Segen).
Es sei denn, und das ist in diesem Beispiel so, man verwendet TypeScript, dann sieht alles anders aus. Eine Alternative zu TypeScript wäre Flow von Facebook (welches dann auch wieder mit Babel zusammenarbeitet), aber da Angular2 schon TypeScript einsetzt, beschränken wir uns darauf.
Gut gepackt ist halb gewandert
Im nächsten Abschnitt wird ohne viel Erläuterung eine ReactJS/TypeScript Entwicklungs-Umgebung zusammengestellt. Das Vorgehen kann natürlich auch für Angular-Entwicklung angewandt werden und grössere Projekte sollten auf jeden Fall Gebrauch machen von den vorgestellten oder ähnlichen Werkzeugen.
Zwischen der ersten Fassung und der Überarbeitung dieses Blogposts haben sich diverse Anpassungen durch neue Versionen ergeben (sowohl für React, als auch für TypeScript).
mkdir react-anagram && cd react-anagram && npm init -f && git init
Core-Bibliotheken installieren:
npm install --save react react-dom
Wie erwähnt wollen wir TypeScript auch für die Applikation mit React verwenden, um Elm in möglichst Nichts nachzustehen:
npm install -g --save typescript npm link typescript npm install typings --global typings install debug --save typings search react typings install dt~react --global --save typings install dt~react-dom --global --save cat typings/index.d.ts
TypeScript-Compiler-Konfiguration (tsconfig.json):
{ "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "target": "ES5" }, "exclude": [ "node_modules", "typings/browser.d.ts", "typings/browser" ] }
Der Inhalt der Datei wurde bereits im letzten Teil erklärt, Schlüsselzeile hier ist der Eintrag „jsx“: „react“ , der den Kompiler anweist, den jsx -Syntax zu erlauben innerhalb von .tsx -Dateien.
Die folgende Html-Datei (index.html ) soll als Einstiegspunkt dienen:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8" /> <title>Hello, React!</title> </head> <body> <div id='main'></div> <script type="text/javascript" src="build/bundle.js" charset="utf-8"></script> </body> </html>
Dafür brauchen wir aber einen Webserver und ein Tool, dass uns alle Source-Files in ein Bundle packt, also zum Beispiel das hervorragende webpack
zusammen mit dem webpack-dev-Server
npm install --save-dev webpack webpack-dev-server ts-loader
Welcher konfiguriert wird mit (webpack.config.js ):
var path = require("path"); var config = { entry: ["./src/Hello.tsx"], output: { path: path.resolve(__dirname, "build"), filename: "bundle.js" }, resolve: { extensions: ["", ".ts", ".tsx", ".js"] }, module: { loaders: [ { test: /\.tsx?$/, loader: "ts-loader", exclude: /node_modules/ } ] } }; module.exports = config;
Webpack wird angewiesen, von der Datei ./src/app.ts ausgehend, allen abhängigen Code in die Datei bundle.js im Verzeichnis build zu packen. Dabei sollen Dateien mit den unter resolve registrierten Endungen erkannt werden. Dateien die in .ts oder .tsx enden, sollen mit dem ts-loader geladen, bzw. übersetzt werden.
Da es aber noch keine Datei ./src/app.ts gibt, müssen wir die noch bereitstellen:
import * as React from "react"; import * as ReactDOM from "react-dom"; import Hello from "./Hello"; ReactDOM.render(<Hello name="React" />, document.getElementById("main"));
Und natürlich unsere erste und einzige Komponente (Hello.tsx):
import * as React from "react"; interface HelloProps { name: string; } class Hello extends React.Component<HelloProps, {}> { render() { return <div>Hello, {this.props.name}</div>; } } export default Hello;
Schuhe schnüren, los geht’s
`webpack-dev-server`
(Dies startet einen Entwicklungs-Webserver, der bei Änderungen die Dateien neu übersetzt und bundelt. Im build-Verzeichnis liegt nun ein File namens bundle.js , welches (unkomprimiert) etwa 675 KB gross ist. Für den produktiven Einsatz würde webpack mit Plugins versehen für minification und uglyfication, mehr dazu jedoch ein anderes Mal)
Der Index wird unter http://localhost:8080 ausgeliefert.
Für bessere Fehlermeldungen im Browser, und die Anzeige der Komponenten in ihrer Baumstruktur, steht das Plugin “React Developer Tools” bereit.
Die endgültige package.json -Datei sieht so aus (etwas aufgeräumt):
{ "name": "react-anagram", "version": "1.0.0", "scripts": { }, "author": "Manuel Baumann <manuel.baumann@noser.com>", "dependencies": { "react": "^15.0.2", "react-dom": "^15.0.2" }, "devDependencies": { "ts-loader": "^0.8.2", "typescript": "^1.8.10" } }
Elm ist etwas gelangweilt, während die anderen zwei sich intensiv über Build und Bundling, TypeScript und Babel, ES2015 und ES2016 unterhalten. Alles Dinge, über die sich Elm eigentlich nicht mehr viele Gedanken machen will.
Aber da sie sich sehr für den virtuellen DOM interessiert, den sie React damals abgeschaut hatte, hört sie trotzdem aufmerksam zu. Denn wenn die beiden über den virtuellen DOM zu debattieren anfangen, könnte es lustig werden.
Für den Fall der Fälle navigiert sie mit dem Browser auf ihrem Smart-Phone mal zu dieser Adresse: https://auth0.com/blog/2016/01/07/more-benchmarks-virtual-dom-vs-angular-12-vs-mithril-js-vs-the-rest/
Damit wären alle Wandervögel vorgestellt und die Grundlagen für dieses Experiment geschaffen. Im nächsten Teil fangen wir damit an, die Datenstrukturen für den Anagrammerator aufzubauen.
Pingback: Noser Blog ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 2 - Kein Picknick - Noser Blog
Pingback: Noser Blog ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 1 - Aufbruch - Noser Blog
Pingback: Noser Blog ManuScripts: Wenn jemand eine Reise tut… Funktionale Programmierung mit Elm - Teil 4 - Über Stock und Stein - Noser Blog