Skip to main content

Middleware

Redux middleware were designed to enable writing side effects logic:

  • I/O: logging, saving files.
  • AJAX HTTP request.
  • Async timer.
  • Modifying state outside of reducer function.
  • Mutating arguments to dispatch function.
  • Generating random numbers or unique random IDs (e.g. uuid()/Math.random()/Date.now()).

Concepts​

每一个 Middleware 可以通过上下文获取:

  • original store:
    • original store.dispatch.
    • get state by store.getState.
    • 通过 dispatch 对象直接发布 action 对象.
  • next 方法: 前一个 Middleware 返回的 dispatch 方法. 当前 Middleware 可以根据自己对 action 的判断和处理结果, 决定是否调用 next 方法 (是否跳过其他 Middleware 的 dispatch), 以及传入什么样的参数.

从而实现如下功能:

  • Execute extra logic when any action is dispatched.
  • Pause, modify, delay, replace, or halt dispatched actions.
  • Write extra code that has access to dispatch and getState.
  • Teach dispatch how to accept other values besides plain action objects, such as functions (action(dispatch, getState, extraArgument)) and promises, by intercepting them and dispatching real action objects instead.

Types​

export interface Middleware<
DispatchExt = object,
S = any,
D extends Dispatch = Dispatch, // type of the dispatch method
> {
ext: DispatchExt
}
import type { Middleware } from 'redux'
import type { RootState } from '../store'

export const exampleMiddleware: Middleware<
object, // Most middleware do not modify the dispatch return value
RootState
> = store => next => (action) => {
const state = store.getState() // correctly typed as RootState
}

Scheduler​

/**
* Schedules actions with { meta: { delay: N } } to be delayed by N milliseconds.
* Makes `dispatch` return a function to cancel the interval in this case.
*/
function timeoutScheduler(store) {
return next => (action) => {
if (!action.meta || !action.meta.delay)
return next(action)

const intervalId = setTimeout(() => next(action), action.meta.delay)

return function cancel() {
clearInterval(intervalId)
}
}
}

Thunk​

// thunk middleware
function thunk(store) {
return next => action =>
typeof action === 'function'
? action(store.dispatch, store.getState)
: next(action)
}

const createStoreWithMiddleware = applyMiddleware(
logger,
thunk,
timeoutScheduler
)(createStore)
const store = createStoreWithMiddleware(combineReducers(reducers))

function addFave(tweetId) {
return (dispatch, getState) => {
if (getState.tweets[tweetId] && getState.tweets[tweetId].liked)
return

dispatch({ type: IS_LOADING })
// Yay, that could be sync or async dispatching
remote.addFave(tweetId).then(
(res) => {
dispatch({ type: ADD_FAVE_SUCCEED })
},
(err) => {
dispatch({ type: ADD_FAVE_FAILED, err })
}
)
}
}

store.dispatch(addFave())

Internals​

  • Raw Middleware: store => next => action => T.
  • middleware(store): next => action => T.
  • middleware(store)(next): action => T.
  • next: action => T.
  • dispatch: action => T.
  • middleware(store)(next), next and dispatch have same function signature: type Dispatch = (action: Action | AsyncAction) => any.
  • After middlewares.forEach, set next to store.dispatch, make new dispatch get all functions from middlewares.
function applyMiddleware(store, middlewares) {
middlewares = middlewares.slice()
middlewares.reverse()

let next = store.dispatch
// Reduce middlewares with reverse order in Redux.
middlewares.forEach(middleware => (next = middleware(store)(next)))

// When user app execute `dispatch` function,
// middlewares execute with forward order.
return Object.assign({}, store, { dispatch: next })
}
import { applyMiddleware, combineReducers, createStore } from 'redux'

// applyMiddleware takes createStore() and returns
// a function with a compatible API.
const createStoreWithMiddleware = applyMiddleware(
logger,
crashReporter
)(createStore)

// Use it like you would use createStore()let todoApp = combineReducers(reducers);
const store = createStoreWithMiddleware(todoApp)