applyMiddleware(...middleware)
概述
Middleware 是建議用於擴充 Redux 的自訂功能。Middleware 讓您可以包裝 store 的 dispatch
方法,以獲得樂趣和利潤。Middleware 的主要功能是可組合性。可以將多個 Middleware 結合在一起,其中每個 Middleware 都不需要知道鏈中之前或之後的內容。
您不應該直接呼叫 applyMiddleware
。Redux Toolkit 的 configureStore
方法 會自動將一組預設的 middleware 加入 store,或接受要加入的 middleware 清單。
middleware 最常見的用途是支援非同步動作,而不需要太多樣板程式碼或依賴像 Rx 這樣的函式庫。它透過讓您除了正常的動作外,還能發送 非同步動作 來達成此目的。
例如,redux-thunk 讓動作建立器透過發送函式來反轉控制。他們會收到 dispatch
作為引數,並可能會非同步地呼叫它。這些函式稱為 thunks。middleware 的另一個範例是 redux-promise。它讓您發送 Promise 非同步動作,並在 Promise 解析時發送正常的動作。
原始的 Redux createStore
方法並不知道 middleware 是什麼 - 它必須透過 applyMiddleware
來設定,才能加入該行為。然而,Redux Toolkit 的 configureStore
方法 會自動預設加入 middleware 支援。
引數
...middleware
(引數):符合 Redux middleware API 的函式。每個 middleware 都會收到Store
的dispatch
和getState
函式作為命名引數,並傳回一個函式。該函式將會收到next
middleware 的 dispatch 方法,並預期傳回一個action
函式,呼叫next(action)
,並使用可能不同的引數,或在不同的時間,或可能根本不呼叫它。鏈中的最後一個 middleware 會收到真正的 store 的dispatch
方法作為next
參數,從而結束鏈。因此,middleware 簽章為({ getState, dispatch }) => next => action
。
傳回
(函式) 套用給定 middleware 的 store 增強器。store 增強器的簽章為 createStore => createStore
,但最簡單的套用方式是將它傳遞給 createStore()
作為最後一個 enhancer
引數。
範例
範例:自訂記錄器 Middleware
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()
。