Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

The road ahead ....

Slide 3

Slide 3 text

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 :)

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Rekapitulation Part 1

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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?'); };

Slide 9

Slide 9 text

Das ungeliebte Kind: Der Build-Prozess

Slide 10

Slide 10 text

Transpiler Bundler

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

It's all about the state

Slide 13

Slide 13 text

UI State

Slide 14

Slide 14 text

Ein Kreislauf

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

UI Action Creator Store Reducers

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

No content

Slide 20

Slide 20 text

Facebook React

Slide 21

Slide 21 text

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 :)

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

= DOM Document Object Model Tree Datenstruktur

Slide 24

Slide 24 text

VirtualDOM rockz
Hallo Welt

Slide 25

Slide 25 text

VirtualDOM rockz
Hallo Welt
html head body div.foo title meta

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

React to the rescue!

Slide 32

Slide 32 text

Virtual DOM DOM

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Genug !

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

Slide 38

Slide 38 text

Aufbau und Semantik einer React Komponente

Slide 39

Slide 39 text

Name der Komponente "Props"

Slide 40

Slide 40 text

Implementierung einer React Komponente

Slide 41

Slide 41 text

// // 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;

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

// 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;

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

// // 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;

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Exkurs: JSX Proprietärer Facebook Standard

Slide 49

Slide 49 text

No content

Slide 50

Slide 50 text

// // 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.

Slide 51

Slide 51 text

// // 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

Slide 52

Slide 52 text

Tipp: React Developer Tools

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

+ React

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

Action Creator Store Daten Funktionalitäten

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

// // `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;

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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);

Slide 66

Slide 66 text

Container- / Presentational-Components

Slide 67

Slide 67 text

Presentational Container Verbindung zum Store? Art Jupp Nope Class Function

Slide 68

Slide 68 text

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".

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

// 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;

Slide 71

Slide 71 text

Advanced Redux

Slide 72

Slide 72 text

Reducer Composition 1

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

Store todos settings ... todoReducer settingsReducer ...

Slide 75

Slide 75 text

// 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;

Slide 76

Slide 76 text

// 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;

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

// 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);

Slide 79

Slide 79 text

Redux Middlewares 2

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

Signatur / API einer Middleware

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

Beispiel Zentrale Logging-Middleware, die alle Actions loggt.

Slide 87

Slide 87 text

// // 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;

Slide 88

Slide 88 text

Installation der Middleware

Slide 89

Slide 89 text

// 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);

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

Exkurs: Seiteneffekte

Slide 92

Slide 92 text

Was ist ein Seiteneffekt?

Slide 93

Slide 93 text

// // 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

Slide 94

Slide 94 text

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.

Slide 95

Slide 95 text

Arten von Seiteneffekten User Interaktion Kommunikation mit Fremdsystemen etc.

Slide 96

Slide 96 text

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);

Slide 97

Slide 97 text

Kein Platz für meine API-Kommunikation :(

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

// // 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}); } }

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

Redux Saga 3

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

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

Slide 106

Slide 106 text

Wie schaut die Container Component aus?

Slide 107

Slide 107 text

// // 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);

Slide 108

Slide 108 text

Auf zur Saga ...

Slide 109

Slide 109 text

// 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;

Slide 110

Slide 110 text

Installation der Saga

Slide 111

Slide 111 text

// 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);

Slide 112

Slide 112 text

Homework

Slide 113

Slide 113 text

No content

Slide 114

Slide 114 text

No content

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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 } } ] }

Slide 117

Slide 117 text

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

Slide 118

Slide 118 text

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/

Slide 119

Slide 119 text

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/

Slide 120

Slide 120 text

Fin. André König [email protected]