JS Application Development Part 2 - React / React-Saga

8682955fc87df72a1c9284b271e9f414?s=47 Sitegeist
May 31, 2016
83

JS Application Development Part 2 - React / React-Saga

Talk by André König

8682955fc87df72a1c9284b271e9f414?s=128

Sitegeist

May 31, 2016
Tweet

Transcript

  1. CLIENT APPLICATION DEVELOPMENT André König sitegeist media solutions GmbH Part

    2 React + Redux advanced
  2. The road ahead ....

  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 :)
  4. Part 3 My Sysmex Technischer Einblick in die Struktur des

    MySysmex Projektes.
  5. Rekapitulation Part 1

  6. WOW! WOW! JavaScript JavaScript sieht nun wie eine sieht nun

    wie eine richtige richtige Programmier- Programmier- sprache aus. sprache aus. – Leute
  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
  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?'); };
  9. Das ungeliebte Kind: Der Build-Prozess

  10. Transpiler Bundler

  11. None
  12. It's all about the state

  13. UI State

  14. Ein Kreislauf

  15. None
  16. UI Action Creator Store Reducers

  17. UI Action Creator Store Reducers 1 click 2 dispatch(action) 3

    (currentState, action) 4 newState 5 state
  18. Glückwunsch! Ihr seid nun Certified ES2015/Redux Ninjas!

  19. None
  20. Facebook React

  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 :)
  22. Welches Problem löst React? Zeit für ein Bilderrätsel!

  23. = DOM Document Object Model Tree Datenstruktur

  24. <!doctype html> <html> <head> <title>VirtualDOM rockz</title> </head> <body> <div class="foo">

    Hallo Welt </div> </body> </html>
  25. <!doctype html> <html> <head> <title>VirtualDOM rockz</title> </head> <body> <div class="foo">

    Hallo Welt </div> </body> </html> html head body div.foo title meta
  26. const $foo = $('.foo'); const $child = $('<div />'); $child.text('Wohoo!');

    $foo.append($child);
  27. const $foo = $('.foo'); const $child = $('<div />'); $child.text('Wohoo!');

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

    $foo.append($child); html head body div.foo title meta 2 Neues Element einhängen div
  29. Teure Operation aber wir haben doch diese schnellen Maschinen, oder?

    Schon aber ...
  30. Browser ist eine Blackbox! Kontrolle über Repaint + Reflow erfordert

    sehr viel Implementierungsaufwand!
  31. React to the rescue!

  32. Virtual DOM DOM

  33. Virtual DOM Virtuelle "in-memory" Repräsentation des eigentlichen DOM. React entscheidet,

    wann welche Elemente im eigentlichen DOM manipuliert werden müssen.
  34. Genug <Theorie/>!

  35. Thinking in React ... ... means "thinking in components" dom

    = component(state)
  36. None
  37. <Head day="Thursday, 10th" month= "December" taskCount={12}/> <TaskList> </TaskList> <Task title="Morning

    Run" check ed due="7:00 AM"/> <Task title="Buy Pizza on the w ay to work" due="8:00 AM"/> <Task title="10AM Meeting" due= "10:00 AM"/> <Task title="Work Lunch with du des" due="12:00 AM"/>
  38. Aufbau und Semantik einer React Komponente

  39. <Head day="Thursday, 10th" month="December" taskCount={12}/> Name der Komponente "Props"

  40. Implementierung einer React Komponente

  41. // // Datei: Head.js // import React from 'react'; class

    Head extends React.Component { render() { const {day, month, taskCount} = this.props; return ( <div> <h1>{day}</h1> <h2>{month}</h2> <p>{taskCount} Task(s)</p> </div> ); } } Head.propTypes = { day: React.PropTypes.string.isRequired, month: React.PropTypes.string.isRequired, taskCount: React.PropTypes.number.isRequired }; export default Head;
  42. Erster Fundamentalsatz Die Props stellen das Daten- und Funktionalitätsinterface der

    Komponente dar.
  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( <MyButton caption="Hallo Welt" onInteraction={onInteraction}/> , app); // Button.js import React from 'react'; class MyButton extends React.Component { render() { const {caption, onInteraction} = this.props; return ( <div onClick={onInteraction}> {caption} </div> ); } } // Interface-Definition MyButton.propTypes = { caption: React.PropTypes.string.isRequired, onInteraction: React.PropTypes.func.isRequired }; export default MyButton;
  44. Exkurs: Lifecycle-Methoden Hooks, die in bestimmten Stadien des Lebenszyklus einer

    Komponente ausgeführt werden
  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 ( <div> <h1>{day}</h1> <h2>{month}</h2> <p>{taskCount} Task(s)</p> </div> ); } } Head.propTypes = { day: React.PropTypes.string.isRequired, month: React.PropTypes.string.isRequired, taskCount: React.PropTypes.number.isRequired }; export default Head;
  46. componentWillMount() componentDidMount() componentWillReceiveProps() shouldComponentUpdate() componentWillUpdate() componentDidUpdate() componentWillUnmount() https://facebook.github.io/react/docs/component-specs.html#lifecycle- methods

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

    ausgeführt.
  48. Exkurs: JSX Proprietärer Facebook Standard

  49. None
  50. // // Datei: Head.js // import React from 'react'; class

    Head extends React.Component { render() { const {day, month, taskCount} = this.props; return ( <div> <h1>{day}</h1> <h2>{month}</h2> <p>{taskCount} Task(s)</p> </div> ); } } export default Head; Wird durch den Transpiler zu gewöhnlichem JavaScript-Code umgewandelt.
  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
  52. Tipp: React Developer Tools

  53. None
  54. + React

  55. Zur Erinnerung Props sind das Daten- und Funktionalitätsinterface der Komponente

    Woher stammen also die Daten? und Was sind die Funktionalitäten?
  56. Action Creator Store Daten Funktionalitäten

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

    die Komponenten binden?
  58. npm install --save react-redux Die offizielle Binding-Bibliothek für die Verbindung

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

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

    auf die Props der Komponente.
  61. // // `ToDoList` Komponente ohne Redux-Binding // import React from

    'react'; class TodoList extends React.Component { render() { const {todos} = this.props; return ( <ul> { /* Iterate over all todos and create new todo items */ todos.map(todo => { <li>{todo}</li> }) } </ul> ); } } TodoList.propTypes = { todos: React.PropTypes.array.isRequired }; export default TodoList;
  62. Szenario: Die todo Daten stehen im Store. Die Komponente soll

    mit dem Store verbunden werden.
  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 ( <ul> { /* Iteration über alle Todo items */ todos.map(todo => { <li>{todo}</li> }) } </ul> ); } } TodoList.propTypes = { todos: React.PropTypes.array.isRequired }; export default connect(mapStateToProps)(TodoList); 1 2 3
  64. The connect decorator's little secret ... Der verbundenen Komponente wird

    automatisch die `dispatch` Funktion übergeben.
  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 ( <ul> { /* Iteration über alle Todo items */ todos.map(todo => { <li onClick={() => dispatch(select(todo.id))}>{todo}</li> }) } </ul> ); } } TodoList.propTypes = { todos: React.PropTypes.array.isRequired, dispatch: React.PropTypes.func.isRequired }; export default connect(mapStateToProps)(TodoList);
  66. Container- / Presentational-Components

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

  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".
  69. Presentational Components Dienen ausschließlich der Repräsentation von Daten Werden in

    der Regel als sogenannte Stateless Functional Components implementiert.
  70. // TodoListItem.js import React from 'react'; const TodoListItem = props

    => <div> {props.title} {props.done ? 'Done' : 'Open'} </div>; 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}) => <div> {title} {done ? 'Done' : 'Open'} </div>; TodoListItem.propTypes = {/* siehe oben */}; export default TodoListItem;
  71. Advanced Redux

  72. Reducer Composition 1

  73. const reducer = (state, action) => { switch (action.type) {

    case 'ADD_TODO': // ... break; case 'REMOVE_TODO': // ... break; case 'MODIFY_SETTING': // ... break; } }; separation of concerns, please!
  74. Store todos settings ... todoReducer settingsReducer ...

  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;
  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;
  77. Okay, nun habe ich die Reducer getrennt aber wie sage

    ich Redux dies?
  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);
  79. Redux Middlewares 2

  80. Middlewares stellen ein Erweiterungskonzept für Redux dar. vgl. mit einem

    Extension-/Plugin-Interface
  81. Konzeptionell befinden sich Middlewares zwischen dem Dispatch einer Action und

    bevor diese Action den jeweiligen Reducer erreicht.
  82. UI Action Creator Store Reducers 1 click 2 dispatch(action) 3

    (currentState, action) 4 newState 5 state 2a Middlewares
  83. Anwendungsszenarien Logging Routing Kommunikation mit externen Systemen (z. B. APIs)

    u.v.m. Persistierung des States innerhalb des `localStorage`
  84. Signatur / API einer Middleware

  85. const myMiddlware = store => next => action => {

    // Do something }; export default myMiddleware;
  86. Beispiel Zentrale Logging-Middleware, die alle Actions loggt.

  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;
  88. Installation der Middleware

  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);
  90. Bleibt eine Frage offen: Wie kommuniziere ich denn nun mit

    einer externen API innerhalb meiner Redux Applikation?
  91. Exkurs: Seiteneffekte

  92. Was ist ein Seiteneffekt?

  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
  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.
  95. Arten von Seiteneffekten User Interaktion Kommunikation mit Fremdsystemen etc.

  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);
  97. Kein Platz für meine API-Kommunikation :(

  98. Das Ziel im Umgang mit Seiteneffekten sollte immer sein: Sie

    so weit wie möglich von dem eigentlichen Applikationscode "wegdrücken".
  99. Ideas? Anyone? Ich zeig Euch mal mein Traumszenario :)

  100. Kommunikation mit Fremdsystem besteht eigentlich immer aus drei Action-Typen Intention

    Erfolgsfall Fehlerfall Requested Succeeded Failed
  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}); } }
  102. Aber ... aber. Wo soll ich das platzieren? :( Reducer?

    ActionCreator? Middleware?
  103. Redux Saga 3

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

    Operationen.
  105. Szenario: Laden von ToDos, wenn die Benutzerin auf einen Button

    gedrückt hat.
  106. Wie schaut die Container Component aus?

  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 ( <div> <a onClick={() => dispatch(fetch())}>Todos laden</a> <ul> { todos.length ? <li>Keine Todos sichtbar.</li> : todos.map(todo => { <li>{todo}</li> }) } </ul> </div> ); } export default connect(mapStateToProps)(ToDoList);
  108. Auf zur Saga ...

  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;
  110. Installation der Saga

  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);
  112. Homework

  113. None
  114. None
  115. Bei uns zu Hause steht ein selbstgebauter Temperatur und Luftfeuchtigkeitssensor:

    morpheus-hute0
  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 } } ] }
  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
  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/
  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/
  120. Fin. André König koenig@sitegeist.de