Redux. From twitter hype to production

Post on 07-Jan-2017

7.565 views 6 download

Transcript of 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

CHAPTER 0CHAPTER 0

INTROINTRO

INITIAL POINTINITIAL POINT

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

IDEAL APPIDEAL APP

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

- localization- build tools

CHAPTER 1CHAPTER 1

HYPEHYPE

FLUXFLUX

REDUXREDUX

@dan_abramovReact Europe

REDUXREDUX(state, action) => state

REDUXREDUX

REDUXREDUX

{ type: 'DEPOSIT', value: 10}

ACTIONACTION

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

REDUXREDUX

STATESTATE

state tree

Single source of truth

REDUXREDUX

const store = createStore(reducer, initialState)

store.subscribe(render)

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

STORESTORE

State is read-only

REDUXREDUX

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

VIEWVIEW

REACT AS VIEW LAYERREACT AS VIEW LAYER

REACT AS VIEW LAYERREACT AS VIEW LAYER

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

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()) }}

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

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

SIDE EFFECTSSIDE EFFECTS

SIDE EFFECTSSIDE EFFECTS

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

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

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

SIDE EFFECTS - SIDE EFFECTS - LESS BOILERPLATELESS BOILERPLATE

CHAPTER 2CHAPTER 2

PRODUCTIONPRODUCTION

FEATURE FOLDERSFEATURE FOLDERS

FEATURE FOLDERSFEATURE FOLDERS

view actions reducers

i

i

i

feature1

feature2

feature3

FEATURE FOLDERSFEATURE FOLDERS

view actions reducers

i

i

i

feature1

feature2

feature3

set of standart components

FEATURE FOLDERSFEATURE FOLDERS

view actions reducers

i

i

i

feature1

feature2

feature3

set of standart components

main reducer

FEATURE FOLDERSFEATURE FOLDERS

view actions reducers

i

i

i

feature1

feature2

feature3

set of standart components

main reducer

all actions

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

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

Action CreatorService 1

Service 2

Service 3

A

A

A

SIDE EFFECTS - SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE

Action CreatorService 1

Service 2

Service 3

A

A

A

SIDE EFFECTS - SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE

Action Creator

Action Creator

SIDE EFFECTS - SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE

applyMiddlewares(middleware1, middleware2, ...)

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

async waterfall

SIDE EFFECTS - SIDE EFFECTS - ADVANCED USAGEADVANCED USAGE

CHAPTER 3CHAPTER 3

SAGASAGA

SAGASAGAorchestrating complex/asynchronous operations

SagaService 1

Service 2

Service 3

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

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

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

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

REDUX-SAGA APIREDUX-SAGA API

takeEverytakeLatest

Sagatake put

call

A

Api

Dispatcher

fork

Saga

cancel

CONCLUSIONCONCLUSION

flux redux

redux: (state, action) => state

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

THANK YOU FOR YOURTHANK YOU FOR YOURATTENTIONATTENTION

Ievgen TerpilVyacheslav Pytel

@JenyaTerpil@vyacheslav_de

Slawaq terpiljenya

LeadScanr

You can find animated version here