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)