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

JS Application Development Part 2 - React / React-Saga

Sitegeist
May 31, 2016
120

JS Application Development Part 2 - React / React-Saga

Talk by André König

Sitegeist

May 31, 2016
Tweet

Transcript

  1. 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 :)
  2. WOW! WOW! JavaScript JavaScript sieht nun wie eine sieht nun

    wie eine richtige richtige Programmier- Programmier- sprache aus. sprache aus. – Leute
  3. 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
  4. 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?'); };
  5. UI Action Creator Store Reducers 1 click 2 dispatch(action) 3

    (currentState, action) 4 newState 5 state
  6. 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 :)
  7. <!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
  8. 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
  9. 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
  10. Virtual DOM Virtuelle "in-memory" Repräsentation des eigentlichen DOM. React entscheidet,

    wann welche Elemente im eigentlichen DOM manipuliert werden müssen.
  11. <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"/>
  12. // // 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;
  13. // 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;
  14. // // 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;
  15. // // 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.
  16. // // 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
  17. Zur Erinnerung Props sind das Daten- und Funktionalitätsinterface der Komponente

    Woher stammen also die Daten? und Was sind die Funktionalitäten?
  18. // // `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;
  19. 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
  20. The connect decorator's little secret ... Der verbundenen Komponente wird

    automatisch die `dispatch` Funktion übergeben.
  21. 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);
  22. 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".
  23. Presentational Components Dienen ausschließlich der Repräsentation von Daten Werden in

    der Regel als sogenannte Stateless Functional Components implementiert.
  24. // 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;
  25. const reducer = (state, action) => { switch (action.type) {

    case 'ADD_TODO': // ... break; case 'REMOVE_TODO': // ... break; case 'MODIFY_SETTING': // ... break; } }; separation of concerns, please!
  26. // 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;
  27. // 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;
  28. // 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);
  29. Konzeptionell befinden sich Middlewares zwischen dem Dispatch einer Action und

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

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

    u.v.m. Persistierung des States innerhalb des `localStorage`
  32. const myMiddlware = store => next => action => {

    // Do something }; export default myMiddleware;
  33. // // 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;
  34. // 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);
  35. Bleibt eine Frage offen: Wie kommuniziere ich denn nun mit

    einer externen API innerhalb meiner Redux Applikation?
  36. // // 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
  37. 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.
  38. 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);
  39. Das Ziel im Umgang mit Seiteneffekten sollte immer sein: Sie

    so weit wie möglich von dem eigentlichen Applikationscode "wegdrücken".
  40. // // 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}); } }
  41. // // 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);
  42. // 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;
  43. // 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);
  44. 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 } } ] }
  45. 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
  46. 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/
  47. 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/