Redux. From twitter hype to production
Transcript of Redux. From twitter hype to production
You can find animated version here
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(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
VIEW LAYERVIEW LAYER
redux
react-redux ng-redux ........
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