Moderne JavaScript Entwicklung

Übersicht

  1. JavaScript
  2. React

Thomas Pasch für SiteOS

Teil 1: JavaScript

  1. Grundlagen
  2. ES6
  3. Typescript
  4. Entwicklungsumgebung

Thomas Pasch für SiteOS

Teil 2: React

  1. React Grundlagen
  2. React Komponenten
  3. Flux Grundlagen
  4. Alt Flux Implementierung

Thomas Pasch für SiteOS

Historie

  • Juni 1997: Erste Version von Netscape für Navigator 2.0
  • Juni 2011: ES3
  • Juni 2015: ES5
  • Juni 2016: ES6 (eigentlich ES2015)
  • Arbeiten an ES7 (eigentlich ES2016) haben bereits begonnen

Traditionelle JS Entwicklung

  • AJAX und XMLHttpRequest: Anpassung/Updates der vom Server gelieferten Seiten
  • Code wird im wesentlichen selbst geschrieben
  • Kein Build System oder Build System vom Server
  • JQuery
  • JavaScript wie im Browser
  • Browserunterschiede müssen im Code beachtet werden
  • Nicht (ganz) veraltet: Wird für Content-getriebene Seiten immer noch verwendet

Browserunterstützung

  • Alle Browser unterstützen heute ES5 vollständig
  • ES6 Unterstützung weit fortgeschritten
    • Muss teilweise konfiguriert werden
    • Fast komplett in Firefox, Chrome und Edge
    • Gute Nacht, IE11!

ES5 Feature

  • Nur wenige Änderungen, hauptsächlich Standarisierung
  • Strict mode ('use strict';)
    • this wird nicht an das gobale Object gebunden
    • Keine impliziten globalen Variablen in Funktionen
    • Einschränkungen bei eval
    • arguments.caller und arguments.callee sind verboten
    • Doppelte Namen in Objekt Literalen und in Funktionsparametern führen zu Fehler
  • Trailing comma
  • Metaprogrammierung
    • Object.create()
    • Property Descriptors: defineProperty(), …
    • Property Iteration: keys(), getOwnPropertyNames()
    • Protecting Properties: preventExtensions(), seal(), freeze(), …
    • Function.prototype.bind()
  • Zusätzliche Array Funktionen: every, filter, forEach, map, …
  • JSON (parse und stringify)

Quintessenz ES5

  • Normalerweise sollte man ES6 verwenden
  • Wenn schon ES5, dann mit 'use strict';
  • Bitte keine ES3 spezifischen Konstrukte

NodeJS

  • Serverseitige Ausführung von JS Code
  • Ermöglicht JS ausserhalb des Browsers auszuführen
  • Distribution enthält auch den npm Paketmanager
  • Node ist die Grundlage JS Entwicklungs-Infrastruktur
    (Builder, Packer, Tester, Linter, …)
  • Moderne JS Entwicklung ist ohne NodeJS schlicht nicht möglich
  • Technisch: V8 (aus Chrome) + Module + libuv

Grundlagen JavaScript

  • Skalare: boolean, number, string, null, undefined, Symbol (ES6)
  • builtins: Array, Date, Error, Function, JSON, NaN, RegExp, ...
  • Object.prototype
  • Object
    • Properties: value, writable, enumerable, configurable
    • Accessors: get, set, enumerable (for ... in), configurable
    • Prototypenkette
  • Arrays
    • Arrays sind einfach Objekte
    • for ... in ist definiert (siehe enumerable)
  • Array-like Objects
    • length property
    • numbered elements: Die properties sind (Ganz-)Zahlen
    • Beispiel: arguments Variable

JavaScript Objekte

// creates the property x on the global object
x = 42
// creates the property y on the global object, 
// and marks it as non-configurable
var y = 43
myobj = {
  h: 4,
  k: 5,
}
// Dot notation
myobj.h // 4
myobj.nothing // undefined

// Index notation
myobj["h"]
myobj["nothing"]
// x is a property of the global object and can be deleted
delete x       // returns true

// y is not configurable, so it cannot be deleted                
delete y       // returns false 

// delete doesn't affect certain predefined properties
delete Math.PI // returns false 

// user-defined properties can be deleted
delete myobj.h // returns true 

JavaScript Arrays

var fruits = ["Banana", "Orange", "Apple", "Mango"]

// convert to string
document.getElementById("demo").innerHTML = fruits.toString()
// # Banana,Orange,Apple,Mango

fruits.join(" * ")
// # Banana * Orange * Apple * Mango

// Changes the first element of fruits to "Kiwi"
fruits[0] = "Kiwi"

// The first parameter (2) defines 
// the position where new elements should be spliced in.
// The second parameter (0) defines 
// how many elements should be removed.
// The rest of the parameters ("Lemon" , "Kiwi") define 
// the new elements to be added.
fruits.splice(2, 0, "Lemon", "Kiwi")
// # ["Kiwi", "Orange", "Lemon", "Kiwi", "Apple", "Mango"]

// Sorts the elements of fruits 
fruits.sort()

// length of array
fruits.length

JavaScript Module Systeme

Export

// File greetings.js
module.exports = {
  sayHelloInEnglish: function() {
    return "HELLO"
  },
       
  sayHelloInSpanish: function() {
    return "Hola"
  },
}

Import

var greetings = require("./greetings.js")

Module Systeme

  • In ES5 gab es diverse Module Systeme/Implementierungen
    • CommonJS, AMD, Bower, RequireJS
  • Unterschiedlich zwischen Node.js und Browser
  • ES6 vereinheitlicht und vereinfacht

JS Threading Modell

JS Event Loop Courtesy of JS Event Loop Explained.

Non-blocking IO, daher oft Callbacks

    request('http://www.google.com', 
        function(error, response, body) {
            console.log(body)
        })
    console.log('Done!')
    

Event Loop basiert

    while(queue.waitForMessage()){
      queue.processNextMessage()
    }
    
  • Run-to-completion: Eine Funktion kann nicht unterbrochen werden
  • Adding messages: setTimeout
  • Mehrere Threads (iframe, webworker) können nur über Message Pasing kommunizieren (postMessage)
  • Kein Locking!

Wie skaliert das auf dem Server?

  • NodeJS beinhalted ein Cluster Module
  • Mehrere NodeJS Instanzen benutzen auch mehrere Prozessor Kerne (z.B. hinter einem Nginx)
  • Socket.io kann für Realtime Interaktionen von mehreren Client verwendet werden
  • Zudem gibt es Module wie pm2 und loopback

http://www.aaronstannard.com/intro-to-nodejs-for-net-developers/

Node’s true innovation is its evented + asynchronous I/O model.

  • http://stackoverflow.com/questions/2387724/node-js-on-multi-core-machines]
  • http://blog.carbonfive.com/2014/02/28/taking-advantage-of-multi-processor-environments-in-node-js/
  • http://stackoverflow.com/questions/14795145/how-the-single-threaded-non-blocking-io-model-works-in-node-js
  • https://softwareengineeringdaily.com/2015/08/02/how-does-node-js-work-asynchronously-without-multithreading/

REST: Anbindung an den Server

Request

Request URL:https://test.wroomer.com/v1/storage/filter/list
Request Method:GET
Status Code:200 OK
Remote Address:91.xxx.149.xxx:443

Response Header

Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:Set-Cookie
Access-Control-Expose-Headers:Set-Cookie
Cache-Control:no-cache, no-store, max-age=0, must-revalidate
Connection:keep-alive
Content-Encoding:gzip
Content-Type:application/json;charset=UTF-8
Date:Thu, 15 Dec 2016 13:08:28 GMT
Expires:0
Pragma:no-cache
Server:nginx/1.6.2
Transfer-Encoding:chunked
Vary:Accept-Encoding
X-Content-Type-Options:nosniff
X-Frame-Options:SAMEORIGIN
X-XSS-Protection:1; mode=block

Response Body: JSON

{"number_of_filters_found":0,"stored_filters":[]}

Alternativen zu REST

Modernes JavaScript

  • Komplettes HTML wird von JavaScript erzeugt
  • Keine Seiten mehr - Application wird (nur) am Anfang geladen:
    Single Page Application (SPA)
  • Die Applikation benutzt sehr viele JS Komponenten (> 100)
  • Komponentenmanagement und Build System auf Basis von Node.js
  • Browserunterscheide werden durch Polyfills ausgebügelt
  • Serveranbingung über REST oder GraphQL

Polyfills

Browserunterschiede werden mittels Polyfill ausgeglichen

A shim that mimics a future API providing fallback functionality to older browsers.

https://remysharp.com/2010/10/08/what-is-a-polyfill

The No-Nonsense Guide to HTML5 Fallbacks

https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills

Lodash

Reduce LOC and improve clarity of your application logic with Lodash

http://colintoh.com/blog/lodash-10-javascript-utility-functions-stop-rewriting

https://lodash.com/docs/4.17.2

Loop through a nested collection

let ownerArr = [{
    "owner": "Colin",
    "pets": [{"name":"dog1"}, {"name": "dog2"}],
}, {
    "owner": "John",
    "pets": [{"name":"dog3"}, {"name": "dog4"}],
}]

// Array's map method.
ownerArr.map(function(owner){
   return owner.pets[0].name
})

// Lodash
_.map(ownerArr, 'pets[0].name')

Deep-cloning Javascript object

let objA = {
    "name": "colin"
}

// Normal method? Too long. See Stackoverflow for solution: http://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript

// Lodash
let objB = _.cloneDeep(objA)
objB === objA // false

Extending object

let objA = {"name": "colin", "car": "suzuki"}
let objB = {"name": "james", "age": 17}
let objC = {"pet": "dog"}

// Lodash
_.assign(objA, objB, objC)
// {"name": "james", "car": "suzuki", "age": 17, "pet": "dog"}

Removing properties from object

// Naive method: Remove an array of keys from object
Object.prototype.remove = function(arr) {
    let that = this
    arr.forEach(function(key){
        delete(that[key])
    })
}

let objA = {"name": "colin", "car": "suzuki", "age": 17}
objA.remove(['car', 'age'])
objA // {"name": "colin"}

// Lodash
objA = _.omit(objA, ['car', 'age']) // {"name": "colin"}

Cont…

let objA = {"name": "colin", "car": "suzuki", "age": 17}

// Lodash
objA = _.omit(objA, 'car') // {"name": "colin", "age": 17}
objA = _.omit(objA, _.isNumber) // {"name": "colin"}
  • http://stackoverflow.com/questions/21536627/whats-the-difference-between-transform-and-reduce-in-lodash

Fetch

let myImage = document.querySelector('img')

fetch('flowers.jpg')
.then(function(response) {
  return response.blob()
})
.then(function(myBlob) {
  let objectURL = URL.createObjectURL(myBlob)
  myImage.src = objectURL
})
  • Promises sind eine Alternative zur Callback Hölle
  • Mehr im 2. Teil des Vortrages

ES6

  • ES6 (aka ECMAScript 2016) ist die neuste Sprachdefinition
  • Browserhersteller arbeiten an Unterstützung
  • ES6 ist weit verbreitet und hat ES5 fast vollständig verdrängt
  • Möglich durch Verwendung von Transpilern:
    Traceur, Babel (+ core-js), Typescript (+ core-js)
  • https://github.com/lukehoban/es6features
  • http://es6-features.org/

let const destructing

// let and const
function f() {
  {
    let x
    {
      // okay, block scoped name
      const x = "sneaky"
      // error, const
      x = "foo"
    }
    // error, already declared in block
    let x = "inner"
  }
}

// DESTRUCTION

// list matching
let [a, , b] = [1,2,3]

// object matching
let { op: a, lhs: { op: b }, rhs: c }
       = getASTNode()

// object matching shorthand
// binds `op`, `lhs` and `rhs` in scope
let {op, lhs, rhs} = getASTNode()

// Can be used in parameter position
function g({name: x}) {
  console.log(x)
}
g({name: 5})

// Fail-soft destructuring
let [a] = []
a === undefined

// Fail-soft destructuring with defaults
let [a = 1] = []
a === 1

default rest spread template

// Default parameters
function f(x, y=12) {
  // y is 12 if not passed (or passed as undefined)
  return x + y
}
f(3) == 15

// Rest arguments
function f(x, ...y) {
  // y is an Array
  return x * y.length
}
f(3, "hello", true) == 6

// Spread operator
function f(x, y, z) {
  return x + y + z
}
// Pass each elem of array as argument
f(...[1,2,3]) == 6

// String interpolation
let name = "Bob", time = "today"
`Hello ${name}, how are you ${time}?`

Arrows vs. Functions

// Expression bodies
let odds = evens.map(v => v + 1)
let nums = evens.map((v, i) => v + i)
let pairs = evens.map(v => ({even: v, odd: v + 1}))

// Statement bodies
nums.forEach(v => {
  if (v % 5 === 0)
    fives.push(v)
})

// Lexical this
let bob = {
  _name: "Bob",
  _friends: [],
  printFriends() {
    this._friends.forEach(f =>
      console.log(this._name + " knows " + f))
  }
}

this and function

function Person(age) {
    this.age = age
    this.growOld = function() {
        this.age++
    }
}
let person = new Person(1)
setTimeout(person.growOld,1000)

setTimeout(function() { console.log(person.age) },2000)
// 1, should have been 2

this and function

  • Problem: setTimeout(person.growOld,1000) führt growOld im Kontext von window aus
  • Vorher: windows.age === undefined
  • Nachher: windows.age === NaN (wegen undefined + 1)
  • Besser growOld als Arrow Funktion
  • Arrow Funktion bindet an das äußere this (kein späterer Kontext)

Enhanced Objects

let obj = {
    // __proto__
    __proto__: theProtoObj,
    // Shorthand for ‘handler: handler’
    handler,
    // Methods
    toString() {
     // Super calls
     return "d " + super.toString()
    },
    // Computed (dynamic) property names
    [ 'prop_' + (() => 42)() ]: 42
}

Set & Map

// Sets
let s = new Set()
s.add("hello").add("goodbye").add("hello")
s.size === 2
s.has("hello") === true

// Maps
let m = new Map()
m.set("hello", 42)
m.set(s, 34)
m.get(s) == 34

Iteratoren

let fibonacci = {
  [Symbol.iterator]() {
    let pre = 0, cur = 1
    return {
      next() {
        [pre, cur] = [cur, pre + cur]
        return { done: false, value: cur }
      }
    }
  }
}

for (let n of fibonacci) {
  // truncate the sequence at 1000
  if (n > 1000)
    break;
  console.log(n)
}

Generatoren

let fibonacci = {
  [Symbol.iterator]: function*() {
    let pre = 0, cur = 1
    for (;;) {
      let temp = pre
      pre = cur
      cur += temp
      yield cur
    }
  }
}

for (let n of fibonacci) {
  // truncate the sequence at 1000
  if (n > 1000)
    break
  console.log(n)
}

Symbole

let MyClass = (function() {

  // module scoped symbol
  let key = Symbol("key")

  function MyClass(privateData) {
    this[key] = privateData
  }

  MyClass.prototype = {
    doStuff: function() {
      ... this[key] ...
    }
  }

  return MyClass
})()

var c = new MyClass("hello")
// Keine Sichtbarkeit des Properties von außen
c["key"] === undefined

Proxies

// Proxying a normal object
let target = {}
let handler = {
  get: function (receiver, name) {
    return `Hello, ${name}!`
  }
}

let p = new Proxy(target, handler)
p.world === 'Hello, world!'

// Proxying a function object
let target = function () { return 'I am the target' }
let handler = {
  apply: function (receiver, ...args) {
    return 'I am the proxy'
  }
}

let p = new Proxy(target, handler)
p() === 'I am the proxy'

Klassen

class SkinnedMesh extends THREE.Mesh {
  constructor(geometry, materials) {
    super(geometry, materials)
    
    this.idMatrix = SkinnedMesh.defaultMatrix()
    this.bones = []
    this.boneMatrices = []
    //...
  }
  update(camera) {
    //...
    super.update()
  }
  get boneCount() {
    return this.bones.length
  }
  set matrixType(matrixType) {
    this.idMatrix = SkinnedMesh[matrixType]()
  }
  static defaultMatrix() {
    return new THREE.Matrix4()
  }
}
  • ES6 Klassen sind nur ‚Syntactic Sugar‘
  • ES6 ist (weiterhin) prototype-basiert
  • Wichtigste Konsequenz: this ist praktisch immer notwendig
    z.B. kein Zugriff auf Properties ohne this
  • ‚Statics‘ werden in der ‚constructor function‘ definiert

Subclassing Builtins

// Pseudo-code of Array
class Array {
    constructor(...args) { /* ... */ }
    static [Symbol.create]() {
        // Install special [[DefineOwnProperty]]
        // to magically update 'length'
    }
}

// User code of Array subclass
class MyArray extends Array {
    constructor(...args) { super(...args) }
}

// Two-phase 'new':
// 1) Call @@create to allocate object
// 2) Invoke constructor on new instance
let arr = new MyArray()
arr[1] = 12
arr.length == 2

Module

// Defining a module
// lib/math.js
export function sum(x, y) {
  return x + y
}
export let pi = 3.141593

// Using a module
// app.js
import * as math from "lib/math"
alert("2π = " + math.sum(math.pi, math.pi))

// Another way to use the module
// otherApp.js
import {sum, pi} from "lib/math"
alert("2π = " + sum(pi, pi))

Default Module

// lib/mathplusplus.js
export * from "lib/math"
export let e = 2.71828182846
export default function(x) {
    return Math.log(x)
}

// app.js
import ln, {pi, e} from "lib/mathplusplus"
alert("2π = " + ln(e)*pi*2)
  • 3 Arten des Module Imports: *, default und benamt
  • ES6 beinhaltet ein Module System
  • Keine Unterschiede mehr zwischen Node.js und Browser!

JavaScript Feature nach ES6

  • ES7 (aka ES2016)
  • Decorators
  • Bind operator (::)
  • Module loaders
  • Fetch
  • async/await

TypeScript

  • Idee: Compile-time Type Checking
  • ES6 ist echte Untermenge von TS
  • Schrittweise Migration durch any
  • Keine Runtime Checks
  • Kein Runtime Overhead
  • Alternative: flow

Basic Types

Basics

let isDone: boolean = false

let decimal: number = 6
let hex: number = 0xf00d
let binary: number = 0b1010
let octal: number = 0o744

let color: string = "blue"

let list: number[] = [1, 2, 3]

Tuples

// Declare a tuple type
let x: [string, number]
// Initialize it
x = ["hello", 10] // OK
// Initialize it incorrectly
x = [10, "hello"] // Error

console.log(x[0].substr(1)) // OK
console.log(x[1].substr(1)) 
// Error, 'number' does not have 'substr'

Any und Object

let notSure: any = 4
notSure = "maybe a string instead"
notSure = false // okay, definitely a boolean

let notSure: any = 4
notSure.ifItExists() // okay, ifItExists might exist at runtime
notSure.toFixed() 
// okay, toFixed exists (but the compiler doesn't check)

let prettySure: Object = 4
prettySure.toFixed() 
// Error: Property 'toFixed' doesn't exist on type 'Object'.

let list: any[] = [1, true, "free"]

list[1] = 100

Void, Null, Undefined, and Never

function warnUser(): void {
    alert("This is my warning message")
}
let unusable: void = undefined

// Not much else we can assign to these variables!
let u: undefined = undefined
let n: null = null

// Function returning never must have unreachable end point
function error(message: string): never {
    throw new Error(message)
}

// Inferred return type is never
function fail() {
    return error("Something failed")
}

// Function returning never must have unreachable end point
function infiniteLoop(): never {
    while (true) {
    }
}

Type assertions (Casts)

let someValue: any = "this is a string"

let strLength: number = (someValue as string).length

Interfaces

interface SquareConfig {
    color: string // required property
    width?: number  // optional property
}

function createSquare(config: SquareConfig): {color: string; area: number} {
    let newSquare = {color: "white", area: 100}
    if (config.color) {
        newSquare.color = config.color
    }
    if (config.width) {
        newSquare.area = config.width * config.width
    }
    return newSquare
}

let mySquare = createSquare({color: "black"})

Generics

function identity<T>(arg: T): T {
    return arg
}
let output = identity("myString")  // type of output will be 'string'

interface Lengthwise {
    length: number
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
    console.log(arg.length)
    // Now we know it has a .length property, so no more error
    return arg
}
  • https://github.com/Microsoft/TypeScript/wiki/What‘s-new-in-TypeScript
  • https://www.typescriptlang.org/docs/handbook/basic-types.html
  • https://basarat.gitbooks.io/typescript/content/docs/types/typeGuard.html
  • https://www.typescriptlang.org/docs/handbook/symbols.html

Advanced Types

Intersection Types

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{}
    for (let id in first) {
        (result as any)[id] = (<any>first)[id]
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (result as any)[id] = (<any>second)[id]
        }
    }
    return result
}

class Person {
    constructor(public name: string) { }
}
interface Loggable {
    log(): void
}
class ConsoleLogger implements Loggable {
    log() {
        // ...
    }
}
let jim = extend(new Person("Jim"), new ConsoleLogger())
let n = jim.name
jim.log()

Union Types

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value
    }
    if (typeof padding === "string") {
        return padding + value
    }
    throw new Error(`Expected string or number, got '${padding}'.`)
}

padLeft("Hello world", 4) 
// returns "    Hello world"
let indentedString = padLeft("Hello world", true) 
// errors during compilation

Type guards

function isFish(pet: Fish | Bird): pet is Fish {
    return (pet as Fish).swim !== undefined
}

Type Aliases

type Name = string
type NameResolver = () => string
type NameOrResolver = Name | NameResolver
function getName(n: NameOrResolver): Name {
    if (typeof n === "string") {
        return n
    }
    else {
        return n()
    }
}
  • https://www.typescriptlang.org/docs/handbook/advanced-types.html
  • https://www.typescriptlang.org/docs/handbook/modules.html
  • https://basarat.gitbooks.io/typescript/content/docs/async-await.html

  • https://github.com/Microsoft/TypeScript/issues/3203
  • http://blog.wolksoftware.com/working-with-react-and-typescript
  • https://www.typescriptlang.org/docs/handbook/react-&-webpack.html
  • https://github.com/Microsoft/TypeScript/issues/5478

Declaration Merging

2 Interfaces mit selben Namen…

interface Foo {
    doIt()
}
interface Foo {
    doSomething()
    doSomethingDifferent()
}

…werden zu

interface Foo {
    doSomething()
    doSomethingDifferent()
    doIt()
}

Merge-bar

  • Vieles kann gemerged werden
  • Module mit Klassen
  • Funktionen und Enums

Nicht Merge-bar

  • Mehrere Klassen
  • Klassen und Variablen
  • Klassen und Interfaces
  • https://www.typescriptlang.org/docs/handbook/declaration-merging.html
  • https://blog.oio.de/2014/03/21/declaration-merging-typescript/

Definitions Files

declare let foo: number
declare function greet(greeting: string): void

declare namespace myLib {
    function makeGreeting(s: string): string
    let numberOfGreetings: number
}
  • Type Definition File sind notwendig um aus TypeScript normales JavaScript zu benutzen
  • Enthalten Deklarationen
  • declare kann in *.d.ts Dateien weg gelassen werden (d.h. ist implizit)

Direktes Verwendung von JS aus TS

import MyImgJs from '../../Common/MyImg.js'

const MyImg: new() => any = MyImgJs
  • https://typescript.codeplex.com/wikipage?title=Writing%20Definition%20%28.d.ts%29%20Files
  • http://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html

JS Entwicklungsumgebung

Was brauche ich?

  • Browser (=Chrome)
    • Evt. Browser Addons
  • Editor
  • Build System
    • Transpiler
    • Dev Mode Server
    • Packer

Editor/IDE

  • Ein IDE ist nicht unbedingt erforderlich
  • Debugging geschieht meist in der Browser Umgebung
  • Eclipse ist ungeeignet
  • Idea
  • Visual Studio Code
  • Atom
  • Brackets
  • netbeans

Build System

  • grunt, gulp: Low Level (~ant)
    • Idee: Script Support für Task, die bei Build anfallen
  • webpack: High Level
    • Idee: Packe alles ins JS File mittels Loadern
  • Größere Projekte verwenden meist beides

Dev Mode

  • Wegen des Transpilers braucht man Setup für Debugging
  • Dev Mode arbeitet als Server aus dem RAM
  • Support für Hot-Deployment mittels Web Socket
  • z.B. webpack-dev-server

Transpiler

  • Es gibt mehrere Transpiler…
  • … mit unterschidelichen Stärken und Schwächen
  • Brauchen meist einen ES6 Polyfill (core-js) für die Generierung von ES5
  • Bekannte Transpiler:
    Traceur, Babel, Typescript
  • ES5 Generierung von Typescript ist unvollständig!

Packer

  • Packer strukturieren die Dateien fürs Deployment
  • JavaScript wird meist gepackt
  • Map Dateien für JS und CSS
  • Bilder werden in eine Datei zusammengefasst (Icon Fonts)
  • Ergebnis sind wenige große Dateien

Linter

  • Lint für einheitlichen Source Code Style
  • Wichtig aber auch um Fehler zu finden (~'use strict';)
  • Möglich, ‚schlechte‘ JS Konstrukte zu verbieten
  • Editoren haben Unterstützung für Linter
  • Wichtige Linter: eslint und tslint

Teil 2 - React Übersicht

Teil 2: React (und Flux)

  1. React Grundlagen
  2. React Komponenten
  3. Flux Grundlagen

Thomas Pasch für SiteOS

Historie

  • 2011: Walke startet die Entwicklung bei Facebook
  • 2012: Instagram benutzt React
  • 2013: Open Source (3-Clause BSD)
  • 2016: React 15.4.2
  • Vergleich: Angular 1 ab 2010, Angular 2 ab 2015

Scope

  • React ist reiner Presentation Layer (von MVI)
  • React ist eine JS Library (kein Framework)
  • React ist eine Gegenposition zu Angular 1
  • Haupteinsatzgebiet: Single Page Applications (SPA)
  • One-way Datenfluss (Props und State)
  • JSX (als Art Template)
  • Virtual DOM
  • Isomorph (d.h. Server-seitiges Rendering) möglich
  • Komponenten-basiert
  • Template-basiert
  • Komponenten sind ES6 Klassen
  • keine Vererbung
  • Komponenten sind keine DOM Nodes
  • Komponentenzustand read-only

Umfeld

  • Flux (~MVI Pattern)
  • React Native
  • React Router
  • Diverse Widgets (react-tabs, react-paginate, formsy-react, …)
  • classnames

React Native

  • https://facebook.github.io/react-native/
  • http://www.reactnative.com/

React Router

  • https://reacttraining.com/react-router/
  • https://github.com/ReactTraining/react-router
  • https://github.com/ReactTraining/react-router-addons-routes

Sonstiges

  • http://christianalfoni.github.io/javascript/2014/10/22/nailing-that-validation-with-reactjs.html
  • https://github.com/christianalfoni/formsy-react
  • https://facebook.github.io/react/docs/addons.html
  • https://facebook.github.io/react/docs/create-fragment.html

Architektur

Hauptmerkmale

  • Top-Down Datenfluss
  • Besonders geeignet, wenn Zustand in einer unveränderlichen und flachen Datenstruktur

Wie beginne ich?

  1. UI in Komponenten zerlegen
  2. Statische Version des UI in React implementieren
  3. Minimale (aber vollständige) Zustandsrepresentation des UI identifizieren
  4. Zustand aufteilen (Stores, Props, State)
  5. Inversen Datenfluss hinzufügen (mittels Flux)

Woher kommt die Geschwindigkeit?

  • Virtual DOM
  • Leichtgewichtige Komponenten
  • Vergleich zwischen alten und neuen Komponentenbaum
  • Nur Änderungen werden ins ‚richtige‘ DOM eingebaut

Vergleicht mit Angular 2

  • kein Virtual DOM
  • Benutzung von WebWorkern zum Event Dispatch (2 Threads)
  • Ìnterne Verwendung on zone.js: thread-local storage
  • Kann mit ‚Web Components‘ zusammen arbeiten
  • Support for ‚Shadow DOM‘
  • https://facebook.github.io/react/docs/thinking-in-react.html

JSX

  • XML Syntax Erweiterung für JS
  • Wird vom Transpiler in JS übersetzt
  • Transpiler Übersetzung konfigurierbar (React.createElement())
  • Ergebnis: Sprache für Templates, aber volle JS Syntax
  • Kann für HTML verwendet werden

Quellcode

const element: React.ReactNode = (
  <h1 className="greeting">
    Hello, world!
  </h1>
)

Transpiler

const element: React.ReactNode = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
)

Ausführung createElement -> React Element

// Note: this structure is simplified
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world'
  }
}

Keine HTML bzw. JS Injection

const title: string = response.potentiallyMaliciousInput
// This is safe:
const element: React.ReactNode = <h1>{title}</h1>

JSX und JS Ausdrücke

// Inside a React Component ...
getGreeting(user) {
  if (user) {
    return <h1>Hello, {this.formatName(this.user)}!</h1>
  }
  return <img src={this.user.avatarUrl} />
}

formatName(user) {
  return user.firstName + ' ' + user.lastName
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez',
  avatarUrl: 'http://...',
}
  • https://facebook.github.io/react/docs/introducing-jsx.html
  • https://www.typescriptlang.org/docs/handbook/jsx.html
  • https://basarat.gitbooks.io/typescript/content/docs/jsx/tsx.html

Komponenten

  • Props sind Read-Only
  • State niemals direct verändern
  • State Änderungen dürfen asynchron sein
  • State Änderungen werden (flach) verschmolzen
  • Top-Down Datenfluss

Eine einfache Komponente …

interface IWelcome {
    name: string,
}

class Welcome extends React.Component<IWelcome, {}> {
  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

… wird benutzt

const element: React.ReactNode = <Welcome name="Sara" />

ReactDOM.render(
  element,
  document.getElementById('root')
)

… wird mehrmals benutzt

const element: React.ReactNode = (
    <div>
        <Welcome name="Sara" />
        <Welcome name="Cahal" />
        <Welcome name="Edite" />
    </div>
)

ReactDOM.render(
  element,
  document.getElementById('root')
)
interface IClockComponentState {
    date: Date,
}

class Clock extends React.Component<{}, IClockComponentState> {

  timerID: number

  constructor(props) {
    super(props);
    this.state = {date: new Date()}
  }

  componentDidMount() {
    this.timerID = setInterval(
      () => this.tick(),
      1000
    )
  }

  componentWillUnmount() {
    clearInterval(this.timerID)
  }

  tick() {
    this.setState({date: new Date()})
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    )
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
)
  • https://facebook.github.io/react/docs/components-and-props.html
  • https://facebook.github.io/react/docs/state-and-lifecycle.html

Lebenzyklus

  • Mounting: Erzeugung und Einfügen in den DOM Baum
  • Updating: Änderung an Props bzw. State
    (nicht beim 1. Rendern)
  • Unmounting: Element wird aus DOM Baum entfernt
  • Andere APIs: Wichtig, aber nicht direkt Lebenszyklus

Mounting

  • constructor(props):
    • Vor dem Einfügen
    • Zugriff auf Props
    • State kann initialisiert werden
    • Anti-Pattern: State anhand der Props initialisieren

Updating 1

  • componentWillMount():
    • direkt vor dem Einfügen
    • keine Seiteneffekte erlaubt
    • State Änderung mit setState möglich
  • componentDidMount():
    • direkt nach dem Einfügen
    • gute Stelle für Actions (und Store.listen)
    • State Änderung führt zum Re-render()n
  • componentWillReceiveProps(nextProps):
    • Props habe sich möglicherweise geändert
    • Wird nicht beim Mounten aufgerufen
    • State Änderung mit setState möglich

Updating 2

  • shouldComponentUpdate(nextProps, nextState): boolean:
    • default: return true
    • Wird nicht fürs 1. Rendern und
      nicht bei forceUpdate aufgerufen
    • Performance Optimierung

Updating 3

  • componentWillUpdate(nextProps, nextState):
    • direkt vor dem Update, wenn sich Props bzw. State geändert haben
    • nur wenn shouldComponentUpdate true zurück gab
    • State Änderung nicht möglich
  • componentDidUpdate(prevProps, prevState):
    • direkt nach dem Update, wenn sich Props bzw. State geändert haben
    • nur wenn shouldComponentUpdate true zurück gab
    • gute Stelle für manuelle DOM Änderungen (fortgeschritten!)
    • gute Stelle für Actions

Unmounting

  • componentWillUnmount():
    • direkt vor dem Entfernen aus dem DOM Baum
    • cleanup (z.B. Store.unlisten, timer)

Andere APIs

  • setState(nextState, callback?):
    • Niemals State direkt setzen, immer setState benutzen
    • Verursacht einen shallow merge
    • callback ist deprecated
      (ähnlich wie componentDidUpdate)
    • ruft implizit shouldComponentUpdate
    • löst meist ein Re-rendern aus
  • forceUpdate():
    • erzwingt ein Re-rendern
    • ruft nicht shouldComponentUpdate auf
  • https://facebook.github.io/react/docs/react-component.html
  • https://facebook.github.io/react/docs/create-fragment.html
  • https://facebook.github.io/react/docs/lifting-state-up.html

Props und State

Gemeinsamkeiten

  • Bilden gemeinsam den Zustand der Komponente
  • Änderungen bekommt die Komponente ‚automatisch‘ mit
  • Bei Änderungen wird (meist) render ausgelöst
  • Die Komponente sollte den Zustand nicht direkt ändern
  • Zustand sollte in render auch verwendet werden (Ansonsten reicht eine Instanzvariable)

Props

  • Werden durch ein JSX Attribut gesetzt
  • Kommen daher aus der übergeordneten Komponente
  • Read-Only
  • Funktionaler ‚Anteil‘ der Komponente
  • Komponenten nur mit Props (ohne State) sind
    • rein funktional
    • einfach wieder zu verwenden
    • einfach zu verstehen

State

  • Kommt (meist) aus einem Flux-Store
    • subscribe/listen notwendig
    • unsubscribe/unlisten notwendig
  • Geeignet für Seiteneffekte und asynchrone Änderungen
  • Nur über setState ändern
  • ‚shallow merge‘ durch setState
  • https://facebook.github.io/react/docs/react-component.html

Container

  • Wenn benachbarte Komponenten den gleichen Zustand benötigen
  • Dann kann man den Zustand nach oben verschieben
  • Und die Komponenten rein funktional machen
  • Der Container verteilt den Zustand als Props an die inneren Komponenten
  • Ein Pattern, dass in vielen Flux Implementierungen unterstützt wird
  • https://facebook.github.io/react/docs/lifting-state-up.html
  • http://alt.js.org/docs/components/altContainer/

Formulare

  • HTML Form Elemente unterhalten ihren eignen Zustand
  • React möchte aber den gesamten Zustand kontrollieren
  • Mögliche Lösung: React Zustand den Form Elementen aufzwingen
  • React Controlled Components

Beispiel Controlled Component

interface INameFormState {
    value: string,
}

class NameForm extends React.Component<{}, INameFormState> {
  constructor(props) {
    super(props)
    this.state = {value: ''}

    this.handleChange = this.handleChange.bind(this)
    this.handleSubmit = this.handleSubmit.bind(this)
  }

  handleChange(event) {
    this.setState({value: event.target.value})
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value)
    event.preventDefault()
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    )
  }
}

Alternativen

  • Uncontrolled Components
  • Da Formularverarbeitung und Validierung kompliziert ist,
    gibt es dafür Libraries
  • Beispiel: formsy-react
  • https://facebook.github.io/react/docs/forms.html#controlled-components
  • https://facebook.github.io/react/docs/uncontrolled-components.html
  • https://github.com/christianalfoni/formsy-react

Mehrere Komponenten

Informationsfluss äußere zu innerer Komponente

  • Mittels Props einfach möglich
  • Einfach zu verstehen
  • Eingängige Syntax

Informationsfluss innere zu äußerer Komponete

  • Work-around: Mit Props auch Callbacks an innere Komponente übergeben
  • Innere Komponente kann die Callbacks dann aufrufen
  • Deutlich schwieriger zu verstehen
  • Komplizierte Syntax, da für die Callbacks auf ES6 Instanzmethoden bind benötigt wird

Warum Flux?

  • Informationsfluss zwischen direkt umschließenden Komponenten ist kein Problem
  • Aber was, wenn nicht benachbarte Komponenten den gleichen Zustand verwenden?
  • Benötigt wird eine Art MV* Pattern, das möglichst zudem auch den Top-Down Datenfluss unterstützt
  • Facebook hat dafür das Flux Pattern erfunden

Was ist Flux?

  • Flux wurde von Facebook für React entworfen
  • Flux wird vor allem mit React verwendet
  • Reaktion auf das missglückte 2-way-binding aus Angular 1
  • React kann auch ohne Flux verwendet werde
  • https://facebook.github.io/flux/docs/in-depth-overview.html#content
  • https://medium.com/hacking-and-gonzo/flux-vs-mvc-design-patterns-57b28c0f71b7#.oh4jq95t0

Historie

  • Ursprünglich Pattern und Implementierung von Facebook
  • Implementierung ab Mitte 2014 auf github
  • Mittlerweile gibt es viele (bessere!) Flux Implementierungen
  • https://facebook.github.io/flux/docs/in-depth-overview.html
  • https://github.com/facebook/flux/tree/master/examples
  • https://github.com/facebook/flux/graphs/contributors

Scope

  • Flux ist eine alternative zu MV* Pattern
  • Flux und MVI sind sich sehr ähnlich
  • Flux Implementierungen sind Libraries (keine Frameworks)

MVC simple Courtesy of Fluxxor - What is Flux?.

MVC complex Courtesy of Fluxxor - What is Flux?.

Flux simple Courtesy of Fluxxor - What is Flux?.

Flux complex Courtesy of Fluxxor - What is Flux?.

Eigenschaften

  • Erzwingt Synchronität
  • Inversion of Control
  • Semantische Aktionen
  • Keine kaskadierenden Aktionen
  • Top-Down Datenfluss

Implementierungen

  • Besondern 2015 gab es sehr viele, konkurrierende
    Flux Implementierungen
  • Mittlerweile hat sich Redux durchgesetzt
  • Redux verbindet Flux mit einigen weiteren Ideen
  • Für asynchrone Ereignisse benötigt
    Flux zwingend Plug-ins (‚middleware‘)
  • Im Moment gibt es bei der middleware (noch?)
    sehr viel Konkurrenz
  • In Wroomer verwendet: Alt
  • http://reactjs.de/posts/wie-wahlt-man-die-beste-flux-implementierung

Alternativen

  • React kann auch ohne Flux verwendet werden
  • Besonders vielversprechend ist der Einsatz mit
    RxJS (Observable)
  • Mit RxJS kann das Flux Pattern verwendet werden,
    aber es ist keine (spezielle) Implementierung erforderlich

Pro und Contra

Pro

  • React SPAs setzen fast ausschließlich auf Flux
  • Es gibt mehrere ausgereifte Implementierungen,
    aber Redux hat sich durchgesetzt
  • Das Datenflussmodell ist konzeptionell einfach zu verstehen
  • Die typischen Beispiel Apps lassen sich mit
    Flux einfach umsetzen

Kontra

  • Zu viele Implementierungen,
    die sich nur in Details unterscheiden
  • Vielzahl von Möglichkeiten,
    Flux falsch oder schlecht zu verwenden
  • Flux hat Probleme mit Datenreihen und zeitlichen Abfolgen
  • Flux hat Probleme mit Datenabhängigkeiten
  • Flux hat Probleme mit asynchronen Ereignissen
  • Flux ist die Hölle mit asynchronen Datenabhängigkeiten

Pro und Kontra Redux

  • Redux löst die Probleme mit asynchronen Ereignissen
  • Redux gibt einen Weg vor,
    keine Auswahl, wenig Fehlermöglichkeiten
  • Zu viele Middleware Implementierungen, noch kein Standard
  • Redux hat die selben Probleme mit
    Datenreihen und zeitlichen Abfolgen wie Flux allgemein
  • Auch bei Redux sind die Probleme mit
    Datenabhängigkeiten nicht vollständig gelöst

this is

the end

(at this point the grand finale is playing)

reveal-jekyll” by Thomas Friese