本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →
applyMiddleware(...middleware)
概述
中间件是扩展 Redux 自定义功能的标准方式。它允许包装存储的 dispatch 方法以实现灵活控制。中间件的核心特性是可组合性:多个中间件可以串联使用,每个中间件无需知晓链中前后节点的具体实现。
无需直接调用 applyMiddleware。Redux Toolkit 的 configureStore 方法会自动添加默认中间件集合,也可接受自定义中间件列表。
中间件最常见的使用场景是支持异步操作,无需大量模板代码或依赖 Rx 等库。其原理是允许派发异步动作而不仅限于普通动作。
例如 redux-thunk 通过派发函数实现控制反转,这些函数以 dispatch 为参数并可异步调用,此类函数称为 thunks。另一示例 redux-promise 支持派发 Promise 异步动作,并在 Promise 解析时派发普通动作。
原生 Redux createStore 方法不内置中间件支持,需通过 applyMiddleware 启用该功能。而 Redux Toolkit 的 configureStore 方法默认已包含中间件支持。
参数
...middleware(参数): 符合 Redux 中间件 API 的函数。每个中间件接收Store的dispatch和getState方法作为命名参数,并返回新函数。该函数接收链中下一中间件的next派发方法,且需返回能操作action的函数:可修改参数/延迟调用/阻止调用next(action)。链中末位中间件的next参数即真实存储的dispatch方法。中间件签名为:({ getState, dispatch }) => next => action。
返回值
(函数) 应用指定中间件的存储增强器。其签名为 createStore => createStore,推荐作为最后参数传入 createStore() 的 enhancer 形参。
示例
示例:自定义日志中间件
import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'
function logger({ getState }) {
return next => action => {
console.log('will dispatch', action)
// Call the next dispatch method in the middleware chain.
const returnValue = next(action)
console.log('state after dispatch', getState())
// This will likely be the action itself, unless
// a middleware further in chain changed it.
return returnValue
}
}
const store = createStore(todos, ['Use Redux'], applyMiddleware(logger))
store.dispatch({
type: 'ADD_TODO',
text: 'Understand the middleware'
})
// (These lines will be logged by the middleware:)
// will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' ]
示例:使用 Thunk 中间件处理异步动作
import { createStore, combineReducers, applyMiddleware } from 'redux'
import { thunk } from 'redux-thunk'
import * as reducers from './reducers'
const reducer = combineReducers(reducers)
// applyMiddleware supercharges createStore with middleware:
const store = createStore(reducer, applyMiddleware(thunk))
function fetchSecretSauce() {
return fetch('https://www.google.com/search?q=secret+sauce')
}
// These are the normal action creators you have seen so far.
// The actions they return can be dispatched without any middleware.
// However, they only express “facts” and not the “async flow”.
function makeASandwich(forPerson, secretSauce) {
return {
type: 'MAKE_SANDWICH',
forPerson,
secretSauce
}
}
function apologize(fromPerson, toPerson, error) {
return {
type: 'APOLOGIZE',
fromPerson,
toPerson,
error
}
}
function withdrawMoney(amount) {
return {
type: 'WITHDRAW',
amount
}
}
// Even without middleware, you can dispatch an action:
store.dispatch(withdrawMoney(100))
// But what do you do when you need to start an asynchronous action,
// such as an API call, or a router transition?
// Meet thunks.
// A thunk is a function that returns a function.
// This is a thunk.
function makeASandwichWithSecretSauce(forPerson) {
// Invert control!
// Return a function that accepts `dispatch` so we can dispatch later.
// Thunk middleware knows how to turn thunk async actions into actions.
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
)
}
}
// Thunk middleware lets me dispatch thunk async actions
// as if they were actions!
store.dispatch(makeASandwichWithSecretSauce('Me'))
// It even takes care to return the thunk's return value
// from the dispatch, so I can chain Promises as long as I return them.
store.dispatch(makeASandwichWithSecretSauce('My wife')).then(() => {
console.log('Done!')
})
// In fact I can write action creators that dispatch
// actions and async actions from other action creators,
// and I can build my control flow with Promises.
function makeSandwichesForEverybody() {
return function (dispatch, getState) {
if (!getState().sandwiches.isShopOpen) {
// You don't have to return Promises, but it's a handy convention
// so the caller can always call .then() on async dispatch result.
return Promise.resolve()
}
// We can dispatch both plain object actions and other thunks,
// which lets us compose the asynchronous actions in a single flow.
return dispatch(makeASandwichWithSecretSauce('My Grandma'))
.then(() =>
Promise.all([
dispatch(makeASandwichWithSecretSauce('Me')),
dispatch(makeASandwichWithSecretSauce('My wife'))
])
)
.then(() => dispatch(makeASandwichWithSecretSauce('Our kids')))
.then(() =>
dispatch(
getState().myMoney > 42
? withdrawMoney(42)
: apologize('Me', 'The Sandwich Shop')
)
)
}
}
// This is very useful for server side rendering, because I can wait
// until data is available, then synchronously render the app.
import { renderToString } from 'react-dom/server'
store
.dispatch(makeSandwichesForEverybody())
.then(() => response.send(renderToString(<MyApp store={store} />)))
// I can also dispatch a thunk async action from a component
// any time its props change to load the missing data.
import React from 'react'
import { connect } from 'react-redux'
function SandwichShop(props) {
const { dispatch, forPerson } = props
useEffect(() => {
dispatch(makeASandwichWithSecretSauce(forPerson))
}, [forPerson])
return <p>{this.props.sandwiches.join('mustard')}</p>
}
export default connect(state => ({
sandwiches: state.sandwiches
}))(SandwichShop)
使用技巧
-
中间件仅包装存储的
dispatch方法。理论上所有中间件功能均可通过手动包装每个dispatch调用实现,但集中管理更便于全项目范围的动作转换。 -
若在使用
applyMiddleware的同时还使用其他存储增强器,请确保将applyMiddleware置于组合链中它们之前(因其可能含异步操作)。例如应位于 redux-devtools 之前,否则开发者工具无法捕获 Promise 中间件等发出的原始动作。 -
如需按条件应用中间件,请确保仅在需要时导入:
let middleware = [a, b]
if (process.env.NODE_ENV !== 'production') {
const c = require('some-debug-middleware')
const d = require('another-debug-middleware')
middleware = [...middleware, c, d]
}
const store = createStore(
reducer,
preloadedState,
applyMiddleware(...middleware)
)这种做法便于打包工具剔除未使用的模块,从而减小构建产物体积。
-
是否好奇过
applyMiddleware本身的本质?它应该是比中间件更强大的扩展机制。实际上,applyMiddleware正是 Redux 最强大的扩展机制——仓库增强器(store enhancer)的典型实现。编写自定义仓库增强器的需求极为罕见,redux-devtools 是另一个增强器示例。虽然中间件的能力弱于仓库增强器,但其编写难度更低。 -
中间件听起来比实际复杂得多。真正理解中间件的唯一方式是研读现有实现并尝试自行编写。函数嵌套可能令人望而生畏,但大多数中间件实际仅约 10 行代码,正是这种可嵌套组合的特性赋予了中间件系统强大威力。
-
要应用多个 store 增强器,可使用
compose()方法。