React.Redux.Real world.
Rostislav (BABO)
BABO
2
3
4
5
6
FRONTEND
7
Webpack
React, Redux
CSS Modules
8
React
9
Бесконечные списки
Screen gif
10
React-ingrid
11
Отображать только те элементы, которые умещаются на экране
Universal rendering
const html = ReactDOM.renderToString(<App data={data} />)
res.send(html)
12
Блокирующий вызов
Future
React-dom-stream
res.write(`<!DOCTYPE html>`)
const stream = ReactDOMStream.renderToStaticMarkup(<App data={data} />);
stream.pipe(res)
13
Redux
14
DevTools,
Logger,
Time travelling15
Простота
16
Redux
const reducer = (oldState = `FOO`, action) => {
switch(action.type) {
case `UPDATE_FOO`:
return `BAR`
}
return oldState
}
const store = createStore(reducer)
17
// подписаться на обновления
store.subscribe(() => {
assert.ok(store.getState() === `BAR`)
})
// передать изменения
store.dispatch({
type: `UPDATE_FOO`
})
// получить текущее состояние
assert.ok(store.getState() === `FOO`)
Tests
const reducer = (oldState, action) => {
switch(action.type) {
case `ACTION_TYPE`:
return action.payload
}
}
assert.ok(reducer(``, `BAR`) === `BAR`)
18
React + Redux
<Provider store={store}>
<ButtonContainer />
</Provider>
const Button = ({handleClick, title}) => (
<button onClick={handleClick}>{title}</button>
)
19
const mapStateToProps = state => ({
title: state.title
})
const mapDispatchToProps = dispatch => ({
handleClick: () => dispatch({
type: `CLICK`
})
})
const ButtonContainer = connect(mapStateToProps, mapDispatchToProps)(Button)
Immutable
20
spread...
const initialState = {
foo: `foo`,
bar: `bar`
}
const reducer = (state = initialState, action) => {
switch(action.type) {
case `UPDATE_BAR`:
return {
...state,
bar: `baz`
}
}
}
return {
...state,
we: {
...state.we,
must: {
...state.we.must,
go: {
...state.we.must.go,
deeper: action.payload
}
}
}
}
21
Immutable.js
return {
...state,
we: {
...state.we,
must: {
...state.we.must,
go: {
...state.we.must.go,
deeper: action.payload
}
}
}
}
return state.setIn([`we`, `must`, `go`, `deeper`], action.payload)
22
Immutable.js
const mapStateToProps = state => ({
foo: state.get(`foo`),
bar: state.get(`bar`),
...
})
import { Map } from 'immutable'
const map = Map({
foo: `bar`
})
const { foo } = map
assert.fails(foo === `bar`)
assert.ok(foo === undefined)
23
Seamless-immutable
import Immutable from 'seamless-immutable'
const map = Immutable({foo: `bar`})
const { foo } = map
assert.ok(foo === `bar`)
24
Object.freeze()
~5 KB
return state.setIn([`we`, `must`, `go`, `deeper`], action.payload)
Расчеты
25
26
Вычисления
const computeAction = data => {
const result = compute(data)
return {
type: `COMPUTE_ACTION`,
payload: result
}
}
27
Масштабирование
28
state
<Component1 ... />
<Component2 ... />
<Component3 ... />
<Component4 ... />
compute1
compute2
compute3
compute4
Вычисления
29
const mapStateToProps = state => ({
result: compute(state.data)
})
Мемоизация | Reselect
import { createSelector } from 'reselect'
const clustersSelector = createSelector(
state => state.points,
state => state.map.zoom,
(points, zoom) => calculateClusters(points, zoom)
)
30
Actions
31
Flux Standard Action
{
type: `DO_SOMETHING`,
payload: {
foo: `bar`
},
meta: {
foo: `bar`
}
}
32
{
type: `DO_SOMETHING`,
payload: new Error(),
error: true
}
Realtime
REALTIME gif
Синхронизация экранов
33
Plain object
34
store.dispatch(...)
store.dispatch(...)
store.dispatch(...)
store.dispatch(...)
SOCKETS
if(action.meta && action.meta.sync) {
sockets.emit(action)
}
thunk
const asyncAction = () => dispatch => {
dispatch({
type: `REQUEST`
})
fetch()
.then(() => dispatch({type: `REQUEST_SUCCESS`}))
.catch(() => dispatch({type: `REQUEST_ERROR`}))
}
35
thunk??
export const finallySend = () => (dispatch, getState) => {
const {phone, location, latlng, description, uploadId} = getState().toJS()
dispatch({
type: SEND_REQUEST
})
if (isEmpty(latlng)) {
if (!location) {
dispatch(sendResults({phone, location, description, uploadId}))
return dispatch(setStep(`done`))
}
geocodeLocation(location).then(payload => {
const {lat: photoLat, lng: photoLon} = payload
dispatch(sendResults({phone, location, description, uploadId, photoLat, photoLon}))
})
} else {
const {lat: photoLat, lng: photoLon} = latlng
dispatch(sendResults({phone, location, description, uploadId, photoLat, photoLon}))
}
dispatch(setStep(`done`))
}
36
tests?
scale?
SAGA
37
Sagas
38
ON_CLICK
REQUEST
REQUEST_SUCCESS
function* rootSaga() {
yield takeLatest(`CLICK`, request)
}
function* request() {
try {
yield put({type: `REQUEST`})
const payload = yield call(api.requestData)
yield put({type: `REQUEST_SUCCESS`, payload})
} catch(e) {
yield put({type: `REQUEST_ERROR`})
}
}
Тесты
const generator = request()
expect(generator.next().value).toEqual(put({type: `REQUEST`))
expect(generator.next().value).toEqual(call(api.requestData))
expect(generator.next(dummyResponse).value).toEqual(put({type: `REQUEST_SUCCESS`, payload}))
39
Изоляция
40
Проблема?
41
Component
Component
Component
Component
const componentReducer = (state, action) => {
...
case `CLICK`:
return state.set(`clicked`, true)
….
}
cobmineReducers({
component1: componentReducer,
component2: componentReducer
...
})
`CLICK`
Переиспользование компонент
class ReusableComponent extends Component {
constructor() {
this.state = {clicked: false}
}
handleOnClick() {
this.setState({
clicked: true
})
}
render() {
return <button onClick={this.handleOnClick} />
}
}
42
43
redux-state
connectState(
mapLocalStateToProps,
mapLocalDispatchToProps,
mergeProps,
componentReducer
)(Component)
44
import {reducer as states} from `redux-state`
combineReducers({
states,
...
})
redux-multireducer
45
Component
Component
Component
Component
cobmineReducers({
component: multireducer({
`component1`: componentReducer,
`component2`: componentReducer,
`component3`: componentReducer,
...
}),
...
})
`CLICK_reducerKey=component1`
`CLICK_reducerKey=component2`
`CLICK_reducerKey=component3`
saga?
ELM
architecture
46
47
ELM
Model
Update
Command
View
Modularity
REDUX
State
Reducer
Saga
Componet
???
48
Updater
Model
Command
View
elm
49
Parent
Child
Child
redux-elm
//parentUpdater
import { Updater } from 'redux-elm';
import childUpdater, { init as childInit } from './childUpdater'
export const init = () => Immutable({
child1: childInit(),
child2: childInit()
});
export default new Updater(init(), saga)
.case(`Child1`, (model, action) => childUpdater(model.child1, action))
.case(`Child2`, (model, action) => childUpdater(model.child2, action))
.toReducer();
50
redux-elm
//Parent
import { forwardTo, view } from 'redux-elm'
import ChildView from 'redux-elm'
export default view(({ model, dispatch }) => (
<div>
<ChildView model={model} dispatch={forwardTo(dispatch, `Child1`)} />
<ChildView model={model} dispatch={forwardTo(dispatch, `Child2`)} />
</div>
));
51
AMAZING REDUX
seamless-immutable/immutable.js
reselect
redux-saga
redux-elm
52
Спасибо за внимание!
53

Amazing threesome, rrr... React. Redux. Real world / Ростислав Галкин (Babo)