Slide 1

Slide 1 text

UNILATERAL DATA FLOWS in Javascript

Slide 2

Slide 2 text

MATTEO RONCHI @cef62 github.com/cef62 Senior Engineer @ Staff member of Verona Frontenders Italian React & Angular Communities Maintainer

Slide 3

Slide 3 text

ONCE UPON A TIME it was all about Model View Controller

Slide 4

Slide 4 text

Model View Controller User View ViewModel Model MVC MVVM manipulates updates interacts sees databinding mutates reads

Slide 5

Slide 5 text

angular.module(‘my-module’, []) .factory(‘model’, function() { return { updateCount() { this.counter++ } counter: 0 } }) .component(‘myComponent’, { template: ` Click Me {{$ctrl.model.counter}}`, controller(model) { this.model = model this.onClick = () => model.update() } })

Slide 6

Slide 6 text

FLUX PATTERN Frontend Architectures Evolution

Slide 7

Slide 7 text

• Facebook interpretation of some well known patterns, like command and event-sourcing • First modern implementation of a unidirectional data flow architecture • Support multiple stores and envelopes request using actions

Slide 8

Slide 8 text

View Store A Dispatcher Action Creator Actions Web API Store B User Interactions Change Events Pass Data To Stores Web Socket FLUX

Slide 9

Slide 9 text

• Flux complexity grows with the number of stores • Huge code boilerplate required for managing actions and action-creators • No proper abstractions to manage complex async workflows FLUX PROBLEMS

Slide 10

Slide 10 text

FLUX OFFSPRING Reflux Alt Redux NuclearJS Cerebral and many more…

Slide 11

Slide 11 text

–Dan Abramov “Redux is a predictable state container for JavaScript apps”

Slide 12

Slide 12 text

• Currently the most used from the next-gen of Flux-like architectures • Really small Surface API • Rich ecosystem of DX Tools • Recognized from Facebook REDUX

Slide 13

Slide 13 text

View User Interactions REDUX User Reducer A Reducer B Middlewares Actions Web API Web Socket Screen Change Events Pass Data To Store Diagram Inspired 
 by Andrè Staltz Store

Slide 14

Slide 14 text

RX.JS • Can be used to implement Flux-like architectures • Offers powerful operators to create really complex async workflows • Can be used standalone or inside Redux • Have its own framework: Cycle.js

Slide 15

Slide 15 text

UNILATERAL DATA FLOW Key Concepts

Slide 16

Slide 16 text

“Components requests actions and redraws themselves when receive updated data.”
 “Views and data are completely decoupled and unaware of each other.”

Slide 17

Slide 17 text

• Views/Components should be completely decoupled from stores • All mutations should happen through dispatch of actions • Async workflows should be isolated inside actions or middlewares • Stores doesn’t have mutation APIs but only getters and should emit change events

Slide 18

Slide 18 text

const enableLoaderAction = () => ({ type: ENABLE_LOADER }) const syncAction = (doSomethingData) => ({ type: DO_SOMETHING, payload: doSomethingData }) // using redux-thunk syntax const asyncAction = (data) => (dispatcher) => invokeServerAPI(data) .then((res) => dispatcher(syncAction(res))) ACTIONS

Slide 19

Slide 19 text

// using redux reducer syntax const appState = (state, { type, payload }) => { switch(type) { case ENABLE_LOADER: return setLoader(state, true) case DISABLE_LOADER: return setLoader(state, false) case DO_SOMETHING: return onDoSomething(state, payload) default return state } } STORE / REDUCERS

Slide 20

Slide 20 text

const setLoader(state, showLoader) => { return Object.assign({}, state, { showLoader }) } const onDoSomething(state, info) => { return Object.assign({}, state, { info }) } STORE / REDUCERS

Slide 21

Slide 21 text

ASYNC SEQUENCES Real world application often composes more async steps to achieve their goals

Slide 22

Slide 22 text

“There are several ways to gain controls over complex async sequences of actions.”
 “A key point when choosing an async flow management pattern is the composability of different async blocks”

Slide 23

Slide 23 text

THUNKS A thunk is a function that wraps an expression to delay its evaluation. It enables creation of chain of complex async operations. Can be very difficult to maintain for long sequences. Difficult to test.

Slide 24

Slide 24 text

// (…args) => (dispatcher, getState) => {} const thunk = () => (dispatcher, getState) => { // retrieve current state const { info, user } = getState() // activate application loader dispatcher(enableLoader()) // load remote data http.get(`${info.url}/${user.id}`) .then((data) => { // update user data on the store dispatcher(updateUserData(data)) // deactivate application loader dispatcher(disableLoader()) }) }

Slide 25

Slide 25 text

SAGAS An alternative side effect model for Redux apps. You create Sagas to gather all your Side Effects logic in a central place. Highly composable and easy to test. Can be complicate to serialize state with partial sagas execution.

Slide 26

Slide 26 text

// worker Saga: fired on // USER_UPDATE_REQUESTED actions function* fetchUser({ payload }) { try { // load user data from server const user = yield call(API.fetchUser, payload.userId) // pass received data to the store yield put(updateUserData(user)) } catch ({ message }) { // notify the store an error occurred yield put(updateUserError(message)) } } // Saga registered to redux-sagas middleware function* userSaga() { yield* takeLatest(USER_UPDATE_REQUESTED, fetchUser) }

Slide 27

Slide 27 text

OBSERVABLES RxJS-based middleware for Redux. Compose and cancel async actions and more. The means to use reactive programming and composition to create async effects that dispatch actions to your redux reducer(s) Works with observables, promises and es2015 iterables. Highly composable and really terse syntax. Steep learning curve.

Slide 28

Slide 28 text

// actions const fetchUser = (username) => ({ type: FETCH_USER, payload: username }) const fetchUserFulfilled = (payload) => ({ type: FETCH_USER_FULFILLED, payload }) const fetchUserRejected = (payload) => ({ type: FETCH_USER_REJECTED, payload, error: true }) // manager registered to redux-observable const fetchUserManager = (action$) => action$.ofType(FETCH_USER) .switchMap(({ payload }) => ajax.getJSON(`https…/${payload}`) .map(fetchUserFulfilled) .catch(({ xhr }) => of(fetchUserRejected(xhr.response))) )

Slide 29

Slide 29 text

CUSTOM MIDDLEWARE It provides a third-party extension point between dispatching an action, and the moment it reaches the reducer. Can be used for logging, crash reporting, talking to an asynchronous API, routing, and more. Complete control and flexibility. Responsibility to test and maintains all the internal implementation.

Slide 30

Slide 30 text

// action using middleware type const action = () => ({ type: AJAX, url: ‘http://my-api.com', method: 'POST', body: ({ description, title }) => ({ title, description }), cb: (res) => console.log('Done!', res) }) // middleware implementation const middleware = ({ getState }) => (next) => (action) => { // if the type doesn’t match pass action to the next middleware if(action.type !== AJAX) return next(action) // retrieve required fields const {cb, url, method, body } = action // invoke remote endpoint http.get(url, { method, body: JSON.stringify(body(getState())) }) // pass response to action callback .then((response) => cb(response)) }

Slide 31

Slide 31 text

UNILATERAL DATA FLOWS The Good Part

Slide 32

Slide 32 text

• Enable next-gen developer tools (time-travel, advanced loggers) • Remote state serialization to support better issues replication • (potentially) Easy to test • Help decoupling completely data and views management

Slide 33

Slide 33 text

UNILATERAL DATA FLOWS The Bad Part

Slide 34

Slide 34 text

• Require much code boilerplate • Describing application processes is quite verbose (not necessarily a cons) • Works better if the application is strongly data- driven and most of the application states are managed by the store

Slide 35

Slide 35 text

SHOULD I USE A FRAMEWORK to benefit from an unilateral data flow architecture?

Slide 36

Slide 36 text

class App extends React.Component { constructor() { super() this.state = reducer(undefined, {}) } dispatch(action) { this.setState(state => reducer(state, action)) } render() { return ( this.dispatch(increment())} onDecrement={() => this.dispatch(decrement())} /> ) } }

Slide 37

Slide 37 text

WHICH FRAMEWORK I SHOULD USE?

Slide 38

Slide 38 text

• Redux • Cerebral • Cycle • ngStore (angular2 only) • try to learn Rx.js

Slide 39

Slide 39 text

THANKS! @CEF62