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
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)
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)
Вычисления
const computeAction = data => { const result = compute(data)
return { type: `COMPUTE_ACTION`, payload: result }}
27
Масштабирование
28
state
<Component1 ... />
<Component2 ... />
<Component3 ... />
<Component4 ... />
compute1
compute2
compute3
compute4
Мемоизация | Reselect
import { createSelector } from 'reselect'
const clustersSelector = createSelector( state => state.points, state => state.map.zoom, (points, zoom) => calculateClusters(points, zoom))
30
Flux Standard Action
{ type: `DO_SOMETHING`, payload: { foo: `bar` },
meta: { foo: `bar` }}
32
{ type: `DO_SOMETHING`, payload: new Error(), error: true}
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?
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
Проблема?
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
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?
redux-elm
//parentUpdaterimport { 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
//Parentimport { 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