applyMiddleware: extending the Redux store">applyMiddleware: extending the Redux store">
跳至主要内容

applyMiddleware(...middleware)

概述

中间件是使用自定义功能扩展 Redux 的推荐方式。中间件允许您包装商店的 dispatch 方法,以实现各种功能。中间件的关键特性是可组合性。多个中间件可以组合在一起,每个中间件不需要了解链中之前或之后的中间件。

警告

您不应该直接调用 applyMiddleware。Redux Toolkit 的 configureStore 方法 会自动将一组默认的中间件添加到商店,或者可以接受一个要添加的中间件列表。

中间件最常见的用例是支持异步操作,而无需太多样板代码或依赖于像 Rx 这样的库。它通过允许您除了普通操作之外,还调度 异步操作 来实现这一点。

例如,redux-thunk 允许操作创建者通过调度函数来反转控制。它们将接收 dispatch 作为参数,并可能异步调用它。此类函数被称为thunk。另一个中间件示例是 redux-promise。它允许您调度一个 Promise 异步操作,并在 Promise 解决时调度一个普通操作。

原始 Redux createStore 方法默认情况下不理解什么是中间件 - 它必须使用 applyMiddleware 进行配置才能添加该行为。但是,Redux Toolkit 的 configureStore 方法 默认情况下会自动添加中间件支持。

参数

  • ...middleware (参数): 符合 Redux 中间件 API 的函数。每个中间件接收 StoredispatchgetState 函数作为命名参数,并返回一个函数。该函数将被赋予 next 中间件的调度方法,并期望返回一个 action 函数,该函数使用可能不同的参数调用 next(action),或在不同的时间调用,或者可能根本不调用它。链中的最后一个中间件将接收真实存储的 dispatch 方法作为 next 参数,从而结束链。因此,中间件签名为 ({ getState, dispatch }) => next => action

返回值

(函数) 应用给定中间件的存储增强器。存储增强器签名为 createStore => createStore,但最简单的应用方式是将其作为最后一个 enhancer 参数传递给 createStore()

示例

示例:自定义日志记录中间件

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 之前,否则 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 扩展机制(称为 存储增强器)的一个例子。你不太可能需要自己编写存储增强器。另一个存储增强器的例子是 redux-devtools。中间件不如存储增强器强大,但更容易编写。

  • 中间件听起来比实际复杂得多。真正理解中间件的唯一方法是看看现有的中间件是如何工作的,并尝试自己编写一个。函数嵌套可能令人生畏,但你找到的大多数中间件实际上只有 10 行代码,嵌套和可组合性是使中间件系统强大的原因。

  • 要应用多个存储增强器,可以使用 compose()