Redux && Redux Middleware

Motivation

Problem

Flux

Flux vs Redux

How to use Redux

Action、Reducer、React

View

Action

Store

Action

const increment = () => ({ type: "INCREMENT" });

Reducer

function count(state = 0, action) {
    switch (action.type) {
      case "INCREMENT":
        return state + 1;
      default:
        return state;
    }
  }

View

const store = Redux.createStore(count);

const { Provider, connect } = ReactRedux;
  const App = connect(
    state => ({ 
      count: state 
    }),
    dispatch => ({
      increment: () => dispatch(increment()) 
    })
  )(
    class extends React.Component {
      onClick = () => {
        this.props.increment();
      };
      render() {
        return (
          <div>
            <p>Count: {this.props.count}</p>
            <button onClick={this.onClick}>+</button>
          </div>
        );
      }
    }
  );

  ReactDOM.render(
    <Provider store={store}>
      <App />
    </Provider>,
    document.getElementById("app")
  );

Redux CreateStore

createStore

export default function createStore(reducer, preloadedState, enhancer) {
  ...

  enhancer(createStore)(reducer, preloadedState);

  ...

  function dispatch(action) {
    ...

    try {
      isDispatching = true
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    ...

    return action
  }
}

Redux Middleware

Sample

import {
  createStore,
  applyMiddleware,
} from 'redux';
import customMiddleware1 from './customMiddleware1.js';
import customMiddleware2 from './customMiddleware2.js';

const store = createStore(reducer, defaultState, applyMiddleware(
  customMiddleware1,
  customMiddleware2,
));

...

export default function App({
  store,
  history,
}: any) {
  return (
    <Provider store={store}>
      <ConnectedRouter history={history}>
        <MainBoard />
      </ConnectedRouter>
    </Provider>
  );
}

applyMiddleware

export default function applyMiddleware(...middlewares) {
  return (next) => 
    (reducer, initialState) => {
      var store = next(reducer, initialState);
      var dispatch = store.dispatch;
      var chain = [];
      var middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
      };
      chain = middlewares.map(middleware =>
                    middleware(middlewareAPI));
      dispatch = compose(...chain, store.dispatch);
      return {
        ...store,
        dispatch
      };
   };
}

Composing Functions

given:
f(x) = x^2 + 3x + 1
g(x) = 2x
then:
(f ∘ g)(x) = f(g(x)) = f(2x) = 4x^2 + 6x + 1 

var greet = function(x) { return `Hello, ${ x }` };
var emote = function(x) { return `${x} :)` };
var compose = function(f, g) {
  return function(x) {
    return f(g(x));
  }
}
var happyGreeting = compose(greet, emote);
// happyGreeting(“Stanney”) -> Hello, Stanney :)

Currying

var curriedAdd = function(a) {
    return function(b) {
        return a + b;
    };
};
var addTen = curriedAdd(10);
addTen(10); //20

By currying and composing your functions you can create powerful new functions that create a pipeline for data processing.

applyMiddleware

export default function applyMiddleware(...middlewares) {
  return (next) => 
    (reducer, initialState) => {
      var store = next(reducer, initialState);
      var dispatch = store.dispatch;
      var chain = [];
      var middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
      };
      chain = middlewares.map(middleware =>
                    middleware(middlewareAPI));
      dispatch = compose(...chain, store.dispatch); 
      return {
        ...store,
        dispatch
      };
   };
}

applyMiddleware

export default function applyMiddleware(...middlewares) {
  return (next) => 
    (reducer, initialState) => {
      var store = next(reducer, initialState);
      var dispatch = store.dispatch;
      var chain = [];
      var middlewareAPI = {
        getState: store.getState,
        dispatch: (action) => dispatch(action)
      };
      chain = middlewares.map(middleware =>
                    middleware(middlewareAPI));
      dispatch = compose(...chain, store.dispatch); <= !!!!!!
      return {
        ...store,
        dispatch
      };
   };
}

compose

dispatch = compose(...chain, store.dispatch);


export default function compose(...funcs) {
     return funcs.reduceRight((composed, f) => f(composed));
}

=> middlewareI(middlewareJ(middlewareK(store.dispatch)))(action)

Demo

You know nothing...

#1

store.dispatch(stanneyTodo('Make Report'))

==

const action = stanneyTodo('Make Report')

console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())

#2

store.dispatch(stanneyTodo('Make Report'))

==

function dispatchAndLog(store, action) {
  console.log('dispatching', action)
  store.dispatch(action)
  console.log('next state', store.getState())
}

dispatchAndLog(store, stanneyTodo('Make Report'))

#3

store.dispatch(stanneyTodo('Make Report'))

==

const next = store.dispatch
store.dispatch = function dispatchAndLog(action) {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

#4 - mutiple middleware?

function showLogging(store) {
  const next = store.dispatch
  store.dispatch = function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

function showCrashReporting(store) {
  const next = store.dispatch
  store.dispatch = function dispatchAndReportErrors(action) {
    try {
      return next(action)
    } catch (err) {
      console.error('Error', err)
      Raven.captureException(err, {
        extra: {
          action,
          state: store.getState()
        }
      })
      throw err
    }
  }
}

#4 - mutiple middleware?

showLogging(store)
showCrashReporting(store)

function showLogging(store) {
  const next = store.dispatch
  store.dispatch = function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

function showCrashReporting(store) {
  const next = store.dispatch
  store.dispatch = function dispatchAndReportErrors(action) {
    try {
      return next(action)
    } catch (err) {
      console.error('Error', err)
      Raven.captureException(err, {
        extra: {
          action,
          state: store.getState()
        }
      })
      throw err
    }
  }
}

#5

function logger(store) {
  const next = store.dispatch

  // Before:
  // store.dispatch = function dispatchAndLog(action) {

  return function dispatchAndLog(action) {
    console.log('dispatching', action)
    let result = next(action)
    console.log('next state', store.getState())
    return result
  }
}

function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()

  // 在每一個 middleware 中變換 dispatch
  middlewares.forEach(middleware => (store.dispatch = middleware(store)))
}


applyMiddleware(store, [logger, crashReporter])

#6

function logger(store) {
  return function wrapDispatchToAddLogging(next) {
    return function dispatchAndLog(action) {
      console.log('dispatching', action)
      let result = next(action)
      console.log('next state', store.getState())
      return result
    }
  }
}

function applyMiddleware(store, middlewares) {
  middlewares = middlewares.slice()
  middlewares.reverse()

  // 在每一個 middleware 中變換 dispatch
  middlewares.forEach(middleware => (store.dispatch = middleware(store)))
}


applyMiddleware(store, [logger, crashReporter])

#7

const logger = store => next => action => {
  console.log('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  return result
}

const crashReporter = store => next => action => {
  try {
    return next(action)
  } catch (err) {
    console.error('Caught an exception!', err)
    Raven.captureException(err, {
      extra: {
        action,
        state: store.getState()
      }
    })
    throw err
  }
}

Redux

By Stanney Yen

Redux

  • 303