Redux. From twitter hype to production

51

Transcript of Redux. From twitter hype to production

Page 1: Redux. From twitter hype to production
Page 3: Redux. From twitter hype to production

ABOUT USABOUT US

- finds leads from social media inreal-timeLeadScanr

Front-End, MLFull stack

Ievgen TerpilVyacheslav Pytel@JenyaTerpil@vyacheslav_de

Page 4: Redux. From twitter hype to production

CHAPTER 0CHAPTER 0

INTROINTRO

Page 5: Redux. From twitter hype to production

INITIAL POINTINITIAL POINT

- Framework without improvements- File structure- Scalability- Build tools

Page 6: Redux. From twitter hype to production

IDEAL APPIDEAL APP

- framework with community- easy to implement new features- easy to support- easy to test

- localization- build tools

Page 7: Redux. From twitter hype to production

CHAPTER 1CHAPTER 1

HYPEHYPE

Page 8: Redux. From twitter hype to production

FLUXFLUX

Page 9: Redux. From twitter hype to production

REDUXREDUX

@dan_abramovReact Europe

Page 10: Redux. From twitter hype to production

REDUXREDUX(state, action) => state

Page 11: Redux. From twitter hype to production

REDUXREDUX

Page 12: Redux. From twitter hype to production

REDUXREDUX

{ type: 'DEPOSIT', value: 10}

ACTIONACTION

Page 13: Redux. From twitter hype to production

REDUXREDUX

function counter(state = 0, action) { switch (action.type) { case 'DEPOSIT': return state + action.value case 'WITHDRAW': return state - action.value default: return state }}

REDUCERREDUCER

Changes are made with pure functions

Page 14: Redux. From twitter hype to production

REDUXREDUX

STATESTATE

state tree

Single source of truth

Page 15: Redux. From twitter hype to production

REDUXREDUX

const store = createStore(reducer, initialState)

store.subscribe(render)

// -----------------------------------store.dispatch(action)

STORESTORE

State is read-only

Page 16: Redux. From twitter hype to production

REDUXREDUX

const App = ({ state }) => { <div> Balance: {state} </div>}

VIEWVIEW

Page 18: Redux. From twitter hype to production

REACT AS VIEW LAYERREACT AS VIEW LAYER

Page 19: Redux. From twitter hype to production

REACT AS VIEW LAYERREACT AS VIEW LAYER

Account = ({ balance, onDepositClick }) => { <div> <button onClick={onDepositClick}> deposit </button> <div>Balance: {balance}</div> </div>}

Page 20: Redux. From twitter hype to production

REACT AS VIEW LAYERREACT AS VIEW LAYERreact-redux

connect( mapStateToProps, mapDispatchToProps)(Account)

function mapStateToProps(state, ownProps) { return { balance: state.balance }}

function mapDispatchToProps(dispatch) { return { onDepositClick: () => dispatch(deposit()) }}

Page 21: Redux. From twitter hype to production
Page 22: Redux. From twitter hype to production

REACT AS VIEW LAYERREACT AS VIEW LAYER

@connect(({ Subscriptions, Profile }) => ({ currentPlan: Subscriptions.get('currentPlan'), userName: Profile.get('userName')}))export default class Subscriptions extends React.Component {

static propTypes = { dispatch: PropTypes.func.isRequired, userName: PropTypes.string, currentPlan: PropTypes.object }

...

}

our case with ES7

decorator

Page 23: Redux. From twitter hype to production

DUMB AND SMARTDUMB AND SMART

Dumb (Presentational)

Presentational and Container Components

Smart (Container)

real view

uses only props

DOM markup and styles

functional components

logic

Redux's connect

binds cb for dumb

DOM markup and styles

reusable

your mini Bootstrap

Page 24: Redux. From twitter hype to production

SIDE EFFECTSSIDE EFFECTS

Page 25: Redux. From twitter hype to production

SIDE EFFECTSSIDE EFFECTS

Page 26: Redux. From twitter hype to production

SIDE EFFECTS - BASE APPROACHSIDE EFFECTS - BASE APPROACH

{ type: 'FETCH_ACCOUNT_REQUEST' }{ type: 'FETCH_ACCOUNT_SUCCESS', account: { ... } }{ type: 'FETCH_ACCOUNT_FAILURE', error: 'Oops' }

function receiveAccount(account) { return { type: FETCH_ACCOUNT_SUCCESS, account }}

actions

action creators

Page 27: Redux. From twitter hype to production

SIDE EFFECTS - BASE APPROACHSIDE EFFECTS - BASE APPROACH

redux-thunk

let getAccount = id => dispatch => { dispatch(requestAccount(id)); return fetch('/account', id) .then(account => dispatch(receiveAccount(account))) .catch(error => dispatch(throwError(error))); };

complex action creator

API request

Page 28: Redux. From twitter hype to production

redux-api-middleware

import { CALL_API } from `redux-api-middleware`;

{ [CALL_API]: { endpoint: 'api/account', method: 'GET', types: ['REQUEST', 'SUCCESS', 'FAILURE'] }}

action creators

SIDE EFFECTS - SIDE EFFECTS - LESS BOILERPLATELESS BOILERPLATE

declarative

Page 29: Redux. From twitter hype to production

SIDE EFFECTS - SIDE EFFECTS - LESS BOILERPLATELESS BOILERPLATE

Page 30: Redux. From twitter hype to production

CHAPTER 2CHAPTER 2

PRODUCTIONPRODUCTION

Page 31: Redux. From twitter hype to production

FEATURE FOLDERSFEATURE FOLDERS

Page 32: Redux. From twitter hype to production

FEATURE FOLDERSFEATURE FOLDERS

view actions reducers

i

i

i

feature1

feature2

feature3

Page 33: Redux. From twitter hype to production

FEATURE FOLDERSFEATURE FOLDERS

view actions reducers

i

i

i

feature1

feature2

feature3

set of standart components

Page 34: Redux. From twitter hype to production

FEATURE FOLDERSFEATURE FOLDERS

view actions reducers

i

i

i

feature1

feature2

feature3

set of standart components

main reducer

Page 35: Redux. From twitter hype to production

FEATURE FOLDERSFEATURE FOLDERS

view actions reducers

i

i

i

feature1

feature2

feature3

set of standart components

main reducer

all actions

Page 36: Redux. From twitter hype to production

FEATURE FOLDERSFEATURE FOLDERSour solutions

import-glob

import reducers from './features/**/reducers.js';

generator-redux-componentyo redux-component

import actions from './features/**/actions.js';

account/

Account.js

x

actions.js

reducres.js

button/

Button.jsx

b-button.scss

Smart (feature) Dump

Page 37: Redux. From twitter hype to production

redux-promise - if it receives a promise, it will dispatch the resolved value of the promise

export function getAccount() { return async (api) => { return { type: events.ACCOUNT_READY, account: await api.options.account.get() }; };}

our case with ES7

SIDE EFFECTS - SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE

Page 38: Redux. From twitter hype to production

Action CreatorService 1

Service 2

Service 3

A

A

A

SIDE EFFECTS - SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE

Page 39: Redux. From twitter hype to production

Action CreatorService 1

Service 2

Service 3

A

A

A

SIDE EFFECTS - SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE

Action Creator

Action Creator

Page 40: Redux. From twitter hype to production

SIDE EFFECTS - SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE

applyMiddlewares(middleware1, middleware2, ...)

Page 41: Redux. From twitter hype to production

redux-chain-middlewaredispatch([ startProcessCard(), setCreditCard(card), getOffers(), buy(plan.get('id')), pushState(null, '/account', {}) ]);

async waterfall

SIDE EFFECTS - SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE

Page 42: Redux. From twitter hype to production

CHAPTER 3CHAPTER 3

SAGASAGA

Page 43: Redux. From twitter hype to production

SAGASAGAorchestrating complex/asynchronous operations

SagaService 1

Service 2

Service 3

Page 44: Redux. From twitter hype to production

REDUX-SAGAREDUX-SAGAGenerator functions (ES6) as action creators1

function* fetchAccount() { const account = yield Api.fetch('/account') console.log(account)}

function* watchFetchAccount() { yield* takeEvery('ACCOUNT_REQUESTED', fetchAccount)}

Page 45: Redux. From twitter hype to production

REDUX-SAGAREDUX-SAGADeclarative Effects

{ CALL: { fn: Api.fetch, args: ['./account'] }}

2

yield only a description of

the function invocation

import { call } from 'redux-saga/effects'

function* fetchAccount() { const account = yield call(Api.fetch, '/account') // ...}

- making our code testable

Page 46: Redux. From twitter hype to production

import { call, put } from 'redux-saga/effects'

function* fetchAccount() { const account = yield call(Api.fetch, '/account') yield put({ type: 'ACCOUNT_RECEIVED', products })}

REDUX-SAGAREDUX-SAGADispatching actions

import { call } from 'redux-saga/effects'

function* fetchAccount(dispatch) { const account = yield call(Api.fetch, '/account') dispatch({ type: 'ACCOUNT_RECEIVED', account })}

Page 47: Redux. From twitter hype to production

assert.deepEqual( iterator.next().value, call(Api.fetch, '/account'))

REDUX-SAGAREDUX-SAGATesting

const iterator = fetchAccount()

// create a fake responseconst account = { balance: 10 }

// expects a dispatch instructionassert.deepEqual( iterator.next(account).value, put({ type: 'ACCOUNT_RECEIVED', account }))}

Page 48: Redux. From twitter hype to production

REDUX-SAGA APIREDUX-SAGA API

takeEverytakeLatest

Sagatake put

call

A

Api

Dispatcher

fork

Saga

cancel

Page 49: Redux. From twitter hype to production

CONCLUSIONCONCLUSION

flux redux

redux: (state, action) => state

Page 50: Redux. From twitter hype to production

CONCLUSIONCONCLUSION

flux redux

redux: (state, action) => state

use feature folders

create collection of Dumb components

side-effects:easy complex

redux-thunkredux-promise

redux-saga

i

Page 51: Redux. From twitter hype to production

THANK YOU FOR YOURTHANK YOU FOR YOURATTENTIONATTENTION

Ievgen TerpilVyacheslav Pytel

@JenyaTerpil@vyacheslav_de

Slawaq terpiljenya

LeadScanr

You can find animated version here