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
reducerfunction. - Mutating arguments to
dispatchfunction. - 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对象.
- original
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
dispatchandgetState. - Teach
dispatchhow 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),nextanddispatchhave same function signature:type Dispatch = (action: Action | AsyncAction) => any.- After
middlewares.forEach, setnexttostore.dispatch, make newdispatchget all functions frommiddlewares.
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)