Upgrade to Pro — share decks privately, control downloads, hide ads and more …

JS Application Development Part 2 - React / React-Saga

Sitegeist
May 31, 2016
110

JS Application Development Part 2 - React / React-Saga

Talk by André König

Sitegeist

May 31, 2016
Tweet

Transcript

  1. CLIENT
    APPLICATION DEVELOPMENT
    André König
    sitegeist media solutions GmbH
    Part 2
    React + Redux advanced

    View Slide

  2. The road ahead ....

    View Slide

  3. Advanced Redux
    Fortgeschrittene Aspekte im Redux-Kontext: Reducer Composition,
    Middlewares, Handling von Seiteneffekten mittels Redux Sagas, etc.
    Part 2
    React
    Einstieg in die View-Layer Implementierung, die innerhalb der
    sitegeist Projekte eingesetzt wird. Was ist React? Kernprinzipien
    hinter der Bibliothek. Wie verbinde ich React mit Redux?
    Wiederholung Part 1
    Kurze Rekapitulation der Themen aus dem ersten Part des
    Workshops / Vortrags. Dies ist der richtige Moment für Rückfragen :)

    View Slide

  4. Part 3
    My Sysmex
    Technischer Einblick in die Struktur des MySysmex Projektes.

    View Slide

  5. Rekapitulation Part 1

    View Slide

  6. WOW!
    WOW! JavaScript
    JavaScript
    sieht nun wie eine
    sieht nun wie eine
    richtige
    richtige Programmier-
    Programmier-
    sprache aus.
    sprache aus.
    – Leute

    View Slide

  7. Variables and Scoping
    Destructuring
    Modules
    Default Values
    Template Strings
    const, let
    const {name} = person;
    const [one, two] = numbers;
    function (foo = 'bar') {}
    const name = `Hallo ${name}`;
    // MyModule.js
    const greeting = name =>
    console.log(`Hallo ${name}`);
    const multiply = (a, b) => a * b;
    export default greeting;
    export {multiply};
    // App.js
    import sayHello from './MyModule';
    import {multiply} from './MyModule';
    sayHello('André'); // => Hallo André
    console.log(multiply(2, 3)); // => 6

    View Slide

  8. Classes
    Arrow Functions
    Object Spread
    ES20**-Feature
    Spread Operator
    // Car.js
    class Car {
    constructor() {}
    moep() {
    console.log('moep');
    }
    }
    const rgb = ['red', 'green', 'blue'];
    const rgbWithYellow = [...rgb, 'yellow'];
    const person = {name: 'André'};
    const withAge = {...person, age: 31};
    const multiply = (a, b) => a * b;
    const hello = name => {
    console.log(`Hello ${name}`);
    console.log('How are you?');
    };

    View Slide

  9. Das ungeliebte Kind: Der Build-Prozess

    View Slide

  10. Transpiler Bundler

    View Slide

  11. View Slide

  12. It's all about the state

    View Slide

  13. UI
    State

    View Slide

  14. Ein Kreislauf

    View Slide

  15. View Slide

  16. UI
    Action
    Creator
    Store Reducers

    View Slide

  17. UI
    Action
    Creator
    Store Reducers
    1
    click
    2 dispatch(action)
    3 (currentState, action)
    4 newState
    5
    state

    View Slide

  18. Glückwunsch!
    Ihr seid nun Certified ES2015/Redux Ninjas!

    View Slide

  19. View Slide

  20. Facebook React

    View Slide

  21. Eine Bibliothek für die Erstellung von
    Benutzungsschnittstellen
    Es ist kein Framework.
    Kann relativ einfach durch andere UI-
    Technologien ersetzt werden ...
    ... das wird auch irgendwann passieren :)

    View Slide

  22. Welches Problem löst React?
    Zeit für ein Bilderrätsel!

    View Slide

  23. =
    DOM
    Document Object Model
    Tree
    Datenstruktur

    View Slide




  24. VirtualDOM rockz



    Hallo Welt



    View Slide




  25. VirtualDOM rockz



    Hallo Welt



    html
    head body
    div.foo
    title
    meta

    View Slide

  26. const $foo = $('.foo');
    const $child = $('');
    $child.text('Wohoo!');
    $foo.append($child);

    View Slide

  27. const $foo = $('.foo');
    const $child = $('');
    $child.text('Wohoo!');
    $foo.append($child);
    html
    head body
    div.foo
    title
    meta
    1 DOM "durchsuchen"
    rote Linien stellen die Suchpfade dar

    View Slide

  28. const $foo = $('.foo');
    const $child = $('');
    $child.text('Wohoo!');
    $foo.append($child);
    html
    head body
    div.foo
    title
    meta
    2 Neues Element einhängen
    div

    View Slide

  29. Teure Operation aber wir haben doch
    diese schnellen Maschinen, oder?
    Schon aber ...

    View Slide

  30. Browser ist eine Blackbox!
    Kontrolle über Repaint + Reflow erfordert
    sehr viel Implementierungsaufwand!

    View Slide

  31. React to the rescue!

    View Slide

  32. Virtual DOM
    DOM

    View Slide

  33. Virtual DOM
    Virtuelle "in-memory" Repräsentation
    des eigentlichen DOM.
    React entscheidet, wann welche Elemente im
    eigentlichen DOM manipuliert werden müssen.

    View Slide

  34. Genug !

    View Slide

  35. Thinking in React ...
    ... means "thinking in components"
    dom = component(state)

    View Slide

  36. View Slide

  37. "December" taskCount={12}/>


    ed due="7:00 AM"/>

    "10:00 AM"/>

    View Slide

  38. Aufbau und Semantik einer
    React Komponente

    View Slide


  39. Name der Komponente
    "Props"

    View Slide

  40. Implementierung einer
    React Komponente

    View Slide

  41. //
    // Datei: Head.js
    //
    import React from 'react';
    class Head extends React.Component {
    render() {
    const {day, month, taskCount} = this.props;
    return (

    {day}
    {month}
    {taskCount} Task(s)

    );
    }
    }
    Head.propTypes = {
    day: React.PropTypes.string.isRequired,
    month: React.PropTypes.string.isRequired,
    taskCount: React.PropTypes.number.isRequired
    };
    export default Head;

    View Slide

  42. Erster Fundamentalsatz
    Die Props stellen das Daten- und Funktionalitätsinterface der Komponente dar.

    View Slide

  43. // App.js
    import React from 'react';
    import {render} from 'react-dom';
    import MyButton from './Button';
    const app = document.getElementById('app');
    const onInteraction = () => console.log('Button wurde gedrückt.');
    render(

    , app);
    // Button.js
    import React from 'react';
    class MyButton extends React.Component {
    render() {
    const {caption, onInteraction} = this.props;
    return (

    {caption}

    );
    }
    }
    // Interface-Definition
    MyButton.propTypes = {
    caption: React.PropTypes.string.isRequired,
    onInteraction: React.PropTypes.func.isRequired
    };
    export default MyButton;

    View Slide

  44. Exkurs: Lifecycle-Methoden
    Hooks, die in bestimmten Stadien des Lebenszyklus
    einer Komponente ausgeführt werden

    View Slide

  45. //
    // Datei: Head.js
    //
    import React from 'react';
    class Head extends React.Component {
    componentDidMount() {
    console.log('Die Head-Komponente wurde dem DOM hinzugefügt.');
    }
    render() {
    const {day, month, taskCount} = this.props;
    return (

    {day}
    {month}
    {taskCount} Task(s)

    );
    }
    }
    Head.propTypes = {
    day: React.PropTypes.string.isRequired,
    month: React.PropTypes.string.isRequired,
    taskCount: React.PropTypes.number.isRequired
    };
    export default Head;

    View Slide

  46. componentWillMount()
    componentDidMount()
    componentWillReceiveProps()
    shouldComponentUpdate()
    componentWillUpdate()
    componentDidUpdate()
    componentWillUnmount()
    https://facebook.github.io/react/docs/component-specs.html#lifecycle-
    methods

    View Slide

  47. Zweiter Fundamentalsatz
    Ändern sich die "Props", so wird render() neu ausgeführt.

    View Slide

  48. Exkurs: JSX
    Proprietärer Facebook Standard

    View Slide

  49. View Slide

  50. //
    // Datei: Head.js
    //
    import React from 'react';
    class Head extends React.Component {
    render() {
    const {day, month, taskCount} = this.props;
    return (

    {day}
    {month}
    {taskCount} Task(s)

    );
    }
    }
    export default Head;
    Wird durch den Transpiler zu gewöhnlichem JavaScript-Code umgewandelt.

    View Slide

  51. //
    // Datei: Head.js
    //
    import React from 'react';
    class Head extends React.Component {
    render() {
    const {day, month, taskCount} = this.props;
    return React.createElement('div',
    React.createElement('h1', day),
    React.createElement('h2', month)
    React.createElement('p', `${taskCount} Task(s)`)
    );
    }
    }
    export default Head;
    Mit anderen Worten: JSX ist ein
    Implementierungsdetail des Transpilers

    View Slide

  52. Tipp: React Developer Tools

    View Slide

  53. View Slide

  54. +
    React

    View Slide

  55. Zur Erinnerung
    Props sind das Daten- und Funktionalitätsinterface der Komponente
    Woher stammen also die Daten?
    und
    Was sind die Funktionalitäten?

    View Slide

  56. Action
    Creator
    Store Daten
    Funktionalitäten

    View Slide

  57. Wie können wir also die Daten und die
    ActionCreator an die Komponenten
    binden?

    View Slide

  58. npm install --save react-redux
    Die offizielle Binding-Bibliothek für die Verbindung zwischen React und Redux.

    View Slide

  59. Der connect Decorator
    "Klebt" die Komponente mit dem Redux-Store zusammen.

    View Slide

  60. connect(mapStateToProps)(MyComponent)
    Funktion für das Mapping der Daten aus dem Store auf die Props der Komponente.

    View Slide

  61. //
    // `ToDoList` Komponente ohne Redux-Binding
    //
    import React from 'react';
    class TodoList extends React.Component {
    render() {
    const {todos} = this.props;
    return (

    {
    /* Iterate over all todos and create new todo items */
    todos.map(todo => {
    {todo}
    })
    }

    );
    }
    }
    TodoList.propTypes = {
    todos: React.PropTypes.array.isRequired
    };
    export default TodoList;

    View Slide

  62. Szenario: Die todo Daten stehen im Store.
    Die Komponente soll mit dem Store verbunden werden.

    View Slide

  63. import React from 'react';
    import {connect} from 'react-redux';
    //
    // Selektiere die Daten aus dem Store, die für diese
    // Komponente relevant sind.
    //
    const mapStateToProps = state => ({
    todos: state.todos
    });
    class TodoList extends React.Component {
    render() {
    const {todos} = this.props;
    return (

    {
    /* Iteration über alle Todo items */
    todos.map(todo => {
    {todo}
    })
    }

    );
    }
    }
    TodoList.propTypes = {
    todos: React.PropTypes.array.isRequired
    };
    export default connect(mapStateToProps)(TodoList);
    1
    2
    3

    View Slide

  64. The connect decorator's little secret ...
    Der verbundenen Komponente wird automatisch die `dispatch` Funktion übergeben.

    View Slide

  65. import React from 'react';
    import {connect} from 'react-redux';
    import {select} from './ActionCreators';
    //
    // Selektiere die Daten aus dem Store, die für diese
    // Komponente relevant sind.
    //
    const mapStateToProps = state => ({
    todos: state.todos
    });
    class TodoList extends React.Component {
    render() {
    const {todos, dispatch} = this.props;
    return (

    {
    /* Iteration über alle Todo items */
    todos.map(todo => {
    dispatch(select(todo.id))}>{todo}
    })
    }

    );
    }
    }
    TodoList.propTypes = {
    todos: React.PropTypes.array.isRequired,
    dispatch: React.PropTypes.func.isRequired
    };
    export default connect(mapStateToProps)(TodoList);

    View Slide

  66. Container- / Presentational-Components

    View Slide

  67. Presentational
    Container
    Verbindung zum Store?
    Art
    Jupp Nope
    Class Function

    View Slide

  68. Container Components
    Sind via `connect` mit dem Store verbunden
    Reichen die Daten an die Kind-Komponenten (Presentational Components) weiter.
    "Dispatchen" Actions mittels `dispatch` Funktion.
    Sind die einzigen Komponenten die Redux "kennen".

    View Slide

  69. Presentational Components
    Dienen ausschließlich der Repräsentation von Daten
    Werden in der Regel als sogenannte Stateless Functional Components implementiert.

    View Slide

  70. // TodoListItem.js
    import React from 'react';
    const TodoListItem = props =>

    {props.title}
    {props.done ? 'Done' : 'Open'}
    ;
    TodoListItem.propTypes = {
    title: React.PropTypes.string.isRequired,
    done: React.PropTypes.bool
    };
    export default TodoListItem;
    oder: Props mittels Destructuring extrahieren:
    // TodoListItem.js
    import React from 'react';
    const TodoListItem = ({title, done}) =>

    {title}
    {done ? 'Done' : 'Open'}
    ;
    TodoListItem.propTypes = {/* siehe oben */};
    export default TodoListItem;

    View Slide

  71. Advanced Redux

    View Slide

  72. Reducer Composition
    1

    View Slide

  73. const reducer = (state, action) => {
    switch (action.type) {
    case 'ADD_TODO':
    // ...
    break;
    case 'REMOVE_TODO':
    // ...
    break;
    case 'MODIFY_SETTING':
    // ...
    break;
    }
    };
    separation of concerns, please!

    View Slide

  74. Store
    todos
    settings
    ...
    todoReducer
    settingsReducer
    ...

    View Slide

  75. // reducers/todos.js
    import {
    ADD_TODO,
    REMOVE_TODO
    } from '../constants/actions';
    const createState = () => ({
    entries: []
    });
    const reducer = (state = createState(), action) {
    switch (action.type) {
    case ADD_TODO: {
    // ...
    }
    case REMOVE_TODO: {
    // ...
    }
    default:
    return state;
    }
    };
    export default reducer;

    View Slide

  76. // reducers/settings.js
    import {
    MODIFY_SETTING
    } from '../constants/actions';
    const createState = () => ({
    grid: true
    });
    const reducer = (state = createState(), action) {
    switch (action.type) {
    case MODIFY_SETTING: {
    // ...
    }
    default:
    return state;
    }
    };
    export default reducer;

    View Slide

  77. Okay, nun habe ich die Reducer getrennt
    aber wie sage ich Redux dies?

    View Slide

  78. // App.js
    import {createStore, combineReducers} from 'redux';
    import todosReducer from './reducers/todos';
    import settingsReducer from './reducers/settings';
    const reducer = combineReducers({
    todos: todosReducer,
    settings: settingsReducer
    });
    const store = createStore(reducer);

    View Slide

  79. Redux Middlewares
    2

    View Slide

  80. Middlewares stellen ein
    Erweiterungskonzept für Redux dar.
    vgl. mit einem Extension-/Plugin-Interface

    View Slide

  81. Konzeptionell befinden sich Middlewares zwischen dem
    Dispatch einer Action und bevor diese Action den
    jeweiligen Reducer erreicht.

    View Slide

  82. UI
    Action
    Creator
    Store Reducers
    1
    click
    2 dispatch(action)
    3 (currentState, action)
    4 newState
    5
    state
    2a Middlewares

    View Slide

  83. Anwendungsszenarien
    Logging
    Routing
    Kommunikation mit externen Systemen (z. B. APIs)
    u.v.m.
    Persistierung des States innerhalb des `localStorage`

    View Slide

  84. Signatur / API einer Middleware

    View Slide

  85. const myMiddlware = store => next => action => {
    // Do something
    };
    export default myMiddleware;

    View Slide

  86. Beispiel
    Zentrale Logging-Middleware, die alle Actions loggt.

    View Slide

  87. //
    // middlewares/logger.js
    //
    const logger = store => next => action => {
    console.log(`Es soll eine Action mit dem Typ: "${action.type}" dispatcht werden.`);
    console.log('Dispatche action', action);
    //
    // Dispatche action
    //
    const result = next(action);
    console.log('Der nächste State ist: ', store.getState());
    return result;
    };
    export default logger;

    View Slide

  88. Installation der Middleware

    View Slide

  89. // App.js
    import {createStore, combineReducers, applyMiddleware} from 'redux';
    import logger from 'middlewares/logger';
    const reducer = combineReducers({/* ... */});
    const middlewares = applyMiddleware(logger);
    const store = createStore(reducer, middlewares);

    View Slide

  90. Bleibt eine Frage offen:
    Wie kommuniziere ich denn nun mit einer
    externen API innerhalb meiner Redux
    Applikation?

    View Slide

  91. Exkurs: Seiteneffekte

    View Slide

  92. Was ist ein Seiteneffekt?

    View Slide

  93. //
    // Beispiel 1
    //
    const add = (a, b) => {
    alert('Hallo Welt');
    return a + b;
    };
    //
    // Beispiel 2
    //
    let a = 2;
    const add = (x, z) =>
    x + z + a;
    add(1, 2); // => 5
    a = 10;
    add(1, 2); // => 13

    View Slide

  94. Im Kontext der funktionalen Programmierung (FP)
    spricht man von impuren Funktionen (Funktionen mit
    Seiteneffekten), wenn die Ergebnismenge nicht stabil ist
    – also mit jedem Aufruf potentiell andere Werte
    zurückliefern kann.
    No. 1 Grund für Bugs, da Seiteneffekte nicht durch
    Unit Tests sauber abgedeckt werden können.

    View Slide

  95. Arten von Seiteneffekten
    User Interaktion
    Kommunikation mit Fremdsystemen
    etc.

    View Slide

  96. In einer Redux + React Anwendung sollte
    alles pur sein.
    Component
    Action
    Creator
    Reducers const state = reducer(currentState, action);
    const action = creator(payload);
    const dom = component(state);

    View Slide

  97. Kein Platz für meine API-Kommunikation :(

    View Slide

  98. Das Ziel im Umgang mit Seiteneffekten sollte immer sein:
    Sie so weit wie möglich von dem eigentlichen
    Applikationscode "wegdrücken".

    View Slide

  99. Ideas? Anyone?
    Ich zeig Euch mal mein Traumszenario :)

    View Slide

  100. Kommunikation mit Fremdsystem besteht
    eigentlich immer aus drei Action-Typen
    Intention Erfolgsfall Fehlerfall
    Requested Succeeded Failed

    View Slide

  101. //
    // Traumszenario (Pseudo-Code)
    //
    if (action.type === 'TODOS_FETCH_REQUESTED') {
    try {
    const todos = communicateWithAPI();
    dispatch({type: 'TODOS_FETCH_SUCCESSFUL', payload: {todos});
    } catch (err) {
    dispatch({type: 'TODOS_FETCH_FAILED', payload: {error: err});
    }
    }

    View Slide

  102. Aber ... aber. Wo soll ich das platzieren? :(
    Reducer? ActionCreator? Middleware?

    View Slide

  103. Redux Saga
    3

    View Slide

  104. Redux Saga ist eine Middleware für die Orchestrierung
    komplexer asynchroner Operationen.

    View Slide

  105. Szenario: Laden von ToDos, wenn die
    Benutzerin auf einen Button gedrückt hat.

    View Slide

  106. Wie schaut die Container Component aus?

    View Slide

  107. //
    // containers/ToDoList.js
    //
    import React from 'react';
    import {connect} from 'react-redux';
    import {fetch} from '../actions';
    const mapStateToProps = ({todos}) => ({
    todos: todos.entries
    });
    class ToDoList extends React.Component {
    render() {
    const {todos, dispatch} = this.props;
    return (

    dispatch(fetch())}>Todos laden

    {
    todos.length ? Keine Todos sichtbar. : todos.map(todo => {
    {todo}
    })
    }


    );
    }
    export default connect(mapStateToProps)(ToDoList);

    View Slide

  108. Auf zur Saga ...

    View Slide

  109. // sagas/todo.js
    import {takeEvery} from 'redux-saga';
    import {call, put} from 'redux-saga/effects';
    import {fetchTodos} from 'my/fancy/todo/api/abstraction/lib';
    import {
    fetchSuccessful,
    fetchFailed
    } from '../actions';
    //
    // Saga für das Abrufen von ToDos über eine entfernte API
    //
    function* work(action) {
    try {
    //
    // `fetchTodos` kapselt die HTTP-Kommunikation mit der API
    //
    const todos = yield call(fetchTodos);
    yield put(fetchSuccessful(todos));
    } catch (err) {
    yield put(fetchFailed(err));
    }
    }
    //
    // Dies ist eine Art Daemon, der die ganze darauf wartet, dass die entsprechende Action dispatcht wurde.
    //
    function* watchTodoFetch() {
    yield* takeEvery('TODO_FETCH_REQUESTED', work);
    }
    export default watchTodoFetch;

    View Slide

  110. Installation der Saga

    View Slide

  111. // App.js
    import {createStore, combineReducers, applyMiddleware} from 'redux';
    import createSagaMiddleware from 'redux-saga'
    import todos from 'sagas/todos';
    const sagas = createSagaMiddleware();
    const reducer = combineReducers({/* ... */});
    const middleware = applyMiddleware(
    sagas
    );
    sagas.run(todos);
    const store = createStore(reducer, middleware);

    View Slide

  112. Homework

    View Slide

  113. View Slide

  114. View Slide

  115. Bei uns zu Hause steht ein selbstgebauter
    Temperatur und Luftfeuchtigkeitssensor:
    morpheus-hute0

    View Slide

  116. morpheus-hute0
    dweet.io
    humidity, temperature
    (alle 5 Minuten)
    Diese Daten lassen sich über dweet.io auch wieder auslesen:
    GET https://dweet.io/get/latest/dweet/for/morpheus-hute0
    {
    "this": "succeeded",
    "by": "getting",
    "the": "dweets",
    "with": [
    {
    "thing": "morpheus-hute0",
    "created": "2016-05-31T05:18:58.786Z",
    "content": {
    "temperature": 21,
    "humidity": 61
    }
    }
    ]
    }

    View Slide

  117. Aufgabe
    Entwickele eine Redux + React Anwendung mit allen in den beiden
    Teilen des Workshops gelernten Aspekten (Container-/Presentational
    Components, Reducers, ActionCreators, Sagas, etc.) um das folgende UI
    zu realisieren:
    Temperatur
    20°C
    Luftfeuchtigkeit
    58 %
    Aktualisieren
    Als Startkit könnt Ihr das folgende leere Projekt klonen:
    https://git.sitegeist.de/koenig/spa-workshop-homework

    View Slide

  118. Ressourcen (1)
    React Dokumentation
    https://facebook.github.io/react/docs/
    React Developer Tools
    Firefox: https://goo.gl/uPhFHS
    Chrome: https://goo.gl/51kvGt
    Redux Dokumentation
    http://redux.js.org
    Redux Saga Dokumentation
    https://yelouafi.github.io/redux-saga/

    View Slide

  119. Ressourcen (2)
    Awesome React
    https://github.com/enaqx/awesome-react
    Awesome Redux
    https://github.com/xgrommx/awesome-redux
    Redux + React Projekte bei sitegeist
    (unter fileadmin/mysysmex)
    MySysmex:
    https://git.sitegeist.de/sitegeist/mysysmex
    MySysmex Reporting: https://git.sitegeist.de/sitegeist
    /mysysmex-reporting
    Sprinter:
    https://git.sitegeist.de/koenig/sprinter
    NeosUI:
    https://github.com/PackageFactory/PackageFactory.Guevara/

    View Slide

  120. Fin.
    André König
    [email protected]

    View Slide