Redux Internals
Vladimir Novick
mail: vnovick@gmail.com
twitter: @VladimirNovick
github: vnovick
facebook: vnovickdev
Frontend Developer & Architect
Redux
Agenda
-
Redux in a nutshell
-
Middleware signature
-
Enhancing store
-
Context wormholes
Redux in a nutshell
Is Flux looks like this?
or...
Well Redux is
So Why Redux?
-
Separation of concerns
-
FP driven
-
Awesome developer experience
Three Principles
-
Single source of truth
-
State is changed only by emitting an action
-
Mutations described by pure functions (reducers)
The data flow
Middleware signatures
Middleware is the suggested way to extend Redux with custom functionality. Middleware lets you wrap the store’s dispatch method for fun and profit
Middleware is a dispatch wrapper
import createLogger from 'redux-logger'
import { createStore } from 'redux'
let store = createStore(rootReducer,
applyMiddleware(thunk, createLogger())
)
Logger
Signature
store => next => action
Middleware
const applyStateTransform = (action) => {
let { transform, state } = action;
return transform ? transform(state) || state : state
}
const fetcher = store => next => action => {
if (action.type === '@@fetcher/FETCH'){
let { url, method, resolve, reject, error } = action.state;
fetch(url, {
method
}).then((result)=> {
let resolveAction = resolve(result);
let errorAction = error(result);
return result.ok ?
next({
type: `@@fetcher/${resolveAction.type}`,
state: applyStateTransform(resolveAction)
}) :
next({
type: `@@fetcher/${errorAction.type}`,
state: applyStateTransform(errorAction)
})
}).catch((err) => {
let rejectAction = reject(err);
return next({
type: `@@fetcher/${rejectAction.type}`,
state: applyStateTransform(rejectAction)
})
});
}
return next(action)
}
////Fetch Action Creator
export const fetchFromServer = (url, method) => dispatch => {
dispatch({
type: '@@fetcher/FETCH',
state: {
url,
method,
resolve: result => {
return {
type: 'FETCH_SUCCESS',
state: result
}
},
reject: err => {
return {
type: 'FETCH_ERROR',
state: err
}
},
error: err => {
return {
type: 'RESPONSE_ERROR',
state: err,
transform: ()=>{
dispatch({
type: 'Lalala'
})
}
}
}
}
})
}
Custom fetch middleware
Enhancing a store
Signature
createStore => createStore
// Usage
componentDidMount(){
let { dispatch } = this.props;
dispatch(inject('player', this))
console.log(appStore.getState(this))
}
//Result will be player stateKey value
//Inject Action Creator
export const inject = (state, component) => {
return {
type: '@@redux/INJECT',
state
component
}
}
function diStoreEnhancer(){
return createStore => (...args) => {
const store = createStore(...args);
const diContainer = []
const dispatch = function decoratedDispatch(action){
if (action.type === '@@redux/INJECT') {
const { component, state } = action;
diContainer.push({ component, stateKey: state })
}
return store.dispatch(action)
}
const getState = function decoratedState() {
if (component) {
return store.getState()[diContainer
.filter(di => di.component === component)
.reduce( (prev,next) => next, {})
.stateKey
];
}
return store.getState()
}
return {
...store,
...{
dispatch,
getState
}
};
};
}
Compose
import { compose } from 'redux'
const createComposedStore = compose(
storeEnhancer(),
applyMiddleware(fetcher, createLogger()
)(createStore);
export default createComposedStore(rootReducer);
Context wormholes
Container(Smart) Component:
Specifies data and behavior for component but does not know anything about appearance
Presentational(Dumb) Component:
Specify appearance of component but knows nothing about behavior
React Components
Passing Store techniques
Passing explicitly via props
The problem:
If you have a component tree, store must be passed through props for all components in the tree.
Passing Store techniques
The solution:
Only container components get store from context when defining contextTypes.
Passing implicitly via Context
<Provider store={store}>
Context
connect(...fns)(component)
React Redux
Provider
import { Provider } from 'react-redux'
import { appStore} from 'appStore'
<Provider store={ appStore }>
<PlayerContainer/>
</Provider>
connect(...fns)(component)
import * from 'actionCreators';
import { connect } from 'react-redux';
// ...
// ...Player React Component definition
function mapStateToProps(state){
const { src, poster, showControls } = state.player;
return {
src, poster, showControls
}
}
export const PlayerContainer = connect(
mapStateToProps
)(Player);
Passing around actions
import React from 'react';
import * from 'actionCreators';
import { connect } from 'react-redux';
class Player extends React.Component {
play(){
this.props.dispatch(actionCreators.play())
}
render() {
return (
<PlayerWrapper>
<PlayerSurface/>
<PlayerControls play={this.play.bind(this}/>
</PlayerWrapper>
)
}
}
//export PlayerContainer with connect as in previous slide
mapDispatchToProps
import React from 'react';
import * from 'actionCreators';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
class Player extends React.Component {
render() {
return (
<PlayerWrapper>
<PlayerSurface/>
<PlayerControls play={this.props.actions.play} />
</PlayerWrapper>
)
}
}
function mapStateToProps(state){
//...same as previous slide
}
function mapDispatchToProps(dispatch){
return {
actions: bindActionCreators(actionCreators, dispatch)
}
}
export const PlayerContainer = connect(
mapStateToProps,
mapDispatchToProps
)(Player);
React-Redux summary
-
Provider: wraps root component and makes possible to use connect()
-
connect: executed on every state change if specified. returned object is merged into component props
- mapStateToProps: executed on every state change if specified. returned object is merged into component props
- mapDispatchToProps: executed on every state change, given dispatch as parameter. returned object will be injected into props. Mostly used together with bindActionCreators
react@90min.com
Redux Internals
By Vladimir Novick
Redux Internals
ReactJS Meetup #5 @Facebook
- 6,901