applyMiddleware(...middlewares)
Middleware 是要用自訂的功能擴充 Redux 的推薦方式。Middleware 可以讓你為了各種理由包裝 store 的 dispatch
method。middleware 的關鍵特色是它是可以組合的。數個 middleware 可以被結合在一起,而每一個 middleware 不需要了解在 middleware 鏈中什麼東西在它的前面或後面。
middleware 最常見的使用案例是不倚靠許多的 boilerplate 程式碼或是依賴像是 Rx 之類的 library 來支援非同步的 actions。它藉由讓你除了能 dispatch 普通 action 以外還能 dispatch async actions 來達成。
例如,redux-thunk 讓 action creator 藉由 dispatch function 反轉控制。它們會接收 dispatch
作為一個參數並可能會非同步的呼叫它。這種 function 被稱作 thunk。另一個 middleware 的例子是 redux-promise。它讓你 dispatch 一個 Promise async action,並在 Promise resolve 時 dispatch 一個一般的 action。
Middleware 沒有被內建在 createStore
裡面而且也不是 Redux 架構的基礎部分,不過我們認為它非常有用所以直接在核心中支援。這樣的話,有一個標準的方式在生態系裡面去擴充 dispatch
,這樣不同的 middleware 就可以在表達力跟實用度上做競爭。
參數
...middlewares
(arguments):符合 Redux middleware API 的 function。每個 middleware 會接收Store
的dispatch
和getState
functions 作為具名參數,並回傳一個 function。這個 function 將會被給予next
middleware 的 dispatch method,並預期會回傳一個action
的 function,可以用不同的參數呼叫next(action)
、或在不同的時間呼叫、或者可能完全不呼叫它。在鏈中的最後一個 middleware 將會接收實際 store 的dispatch
method 作為next
參數,從而結束這條鏈。因此,middleware 的 signature 是({ getState, dispatch }) => next => action
。
回傳
(Function) 一個啟用了給定 middleware 的 store enhancer。store enhancer signature 是 createStore => createStore'
,但最簡單啟用它的方法是把它傳到 createStore()
作為最後一個變數。
範例:客製化 Logger Middleware
import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'
function logger({ getState }) {
return (next) => (action) => {
console.log('will dispatch', action)
// 呼叫在 middleware 鏈的下一個 dispatch method。
let returnValue = next(action)
console.log('state after dispatch', getState())
// 這很有可能是 action 自己,
// 除非在鏈中更後面的 middleware 改變了它。
return returnValue
}
}
let store = createStore(
todos,
[ 'Use Redux' ],
applyMiddleware(logger)
)
store.dispatch({
type: 'ADD_TODO',
text: 'Understand the middleware'
})
// (這幾行將會被 middleware log:)
// 將會 dispatch:{ type: 'ADD_TODO', text: 'Understand the middleware' }
// 在 dispatch 之後的 state:['Use Redux', 'Understand the middleware']
範例:使用 Thunk Middleware 來處理 Async Action
import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
import * as reducers from './reducers'
let reducer = combineReducers(reducers)
// applyMiddleware 會用 middleware 增強 createStore:
let store = createStore(reducer, applyMiddleware(thunk))
function fetchSecretSauce() {
return fetch('https://www.google.com/search?q=secret+sauce')
}
// 有一些你到目前為止所看到的一般 action creator。
// 這些 actions 回傳的東西需不需要任何 middleware 就能被 dispatch。
// 不過,他們只能表達「事實」而不是「非同步資料流」。
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
}
}
// 即使沒有 middleware,你也可以 dispatch action:
store.dispatch(withdrawMoney(100))
// 不過當你需要開始一個非同步 action 時你會怎麼做,
// 像是一個 API 呼叫,或是一個 router transition?
// 迎接 thunk。
// thunk 是一個會回傳一個 function 的 function。
// 這是一個 thunk。
function makeASandwichWithSecretSauce(forPerson) {
// 反轉控制!
// 回傳一個接收 `dispatch` 的 function,所以我們可以在之後進行 dispatch。
// Thunk middleware 知道如何把 thunk async action 轉換成 action。
return function (dispatch) {
return fetchSecretSauce().then(
sauce => dispatch(makeASandwich(forPerson, sauce)),
error => dispatch(apologize('The Sandwich Shop', forPerson, error))
)
}
}
// Thunk middleware 讓我能 dispatch thunk async action
// 就像它們是 action 一樣!
store.dispatch(
makeASandwichWithSecretSauce('Me')
)
// 它甚至會負責回傳 thunk 從 dispatch 回傳的值,
// 所以只要我有回傳 Promise 就可以串接它。
store.dispatch(
makeASandwichWithSecretSauce('My wife')
).then(() => {
console.log('Done!')
})
// 實際上我可以撰寫 action creator,
// 它們從其他的 action creator 來 dispatch action 和 async action,
// 而且我可以用 Promise 來建置我的控制流程。
function makeSandwichesForEverybody() {
return function (dispatch, getState) {
if (!getState().sandwiches.isShopOpen) {
// 你不需要回傳 Promise,不過這是一個方便的慣例
// 這樣呼叫的人總是可以在非同步 dispatch 的結果上呼叫 .then()。
return Promise.resolve()
}
// 我們可以 dispatch 一般物件的 action 以及其他的 thunk,
// 這讓我們可以把非同步的 action 組合在單一一個流程中。
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')
)
)
}
}
// 這對服器端 render 非常有用,因為我可以等到
// 資料準備好,接著同步的 render 應用程式。
import { renderToString } from 'react-dom/server'
store.dispatch(
makeSandwichesForEverybody()
).then(() =>
response.send(renderToString(<MyApp store={store} />))
)
// 我也可以從 component dispatch 一個 thunk async action
// 任何時候它的 props 改變就會去載入缺少的資料。
import { connect } from 'react-redux'
import { Component } from 'react'
class SandwichShop extends Component {
componentDidMount() {
this.props.dispatch(
makeASandwichWithSecretSauce(this.props.forPerson)
)
}
componentWillReceiveProps(nextProps) {
if (nextProps.forPerson !== this.props.forPerson) {
this.props.dispatch(
makeASandwichWithSecretSauce(nextProps.forPerson)
)
}
}
render() {
return <p>{this.props.sandwiches.join('mustard')}</p>
}
}
export default connect(
state => ({
sandwiches: state.sandwiches
})
)(SandwichShop)
提示
Middleware 只包裝了 store 的
dispatch
function。技術上來說,任何 middleware 可以做到的事情,你都可以藉由手動的包裝每一個dispatch
呼叫來做到,不過在一個地方管理比較容易而且能在整個專案的層級上定義 action 轉換。如果你使用其他
applyMiddleware
之外的 store enhancer,請確保有在組合鏈上把applyMiddleware
放在它們前面,因為 middleware 有可能是非同步的。例如,它應該跑在 redux-devtools 前面,因為不這樣做的話 DevTools 無法看到 Promise middleware 以及其他等等的 middleware 發送出來的原生 action。如果你想要條件式的啟用一個 middleware,請確保只有在需要時 import 它:
let middleware = [ a, b ] if (process.env.NODE_ENV !== 'production') { let c = require('some-debug-middleware') let d = require('another-debug-middleware') middleware = [ ...middleware, c, d ] } const store = createStore( reducer, preloadedState, applyMiddleware(...middleware) )
這讓建置工具可以比較簡單的去排除不需要的 module 並減低你的建置的大小。
有沒有想過
applyMiddleware
本身是什麼呢?它理當是一個比 middleware 本身更強大的擴充機制。的確,applyMiddleware
是一個 Redux 最強大的擴充機制叫做 store enhancers 的例子。很有可能你不會想要自己寫一個 store enhancer。另一個 store enhancer 的例子是 redux-devtools。Middleware 沒有 store enhancer 那麼強大,不過它寫起來比較簡單。Middleware 聽起來比它實際上複雜許多。要真正了解 middleware 的唯一方法就是去看現存的 middleware 是如何運作的,並試著撰寫你自己的 middleware。function 巢狀可能會很嚇人,不過你會發現實際上大部份的 middleware 只有大概 10 行左右,而巢狀與組合性是讓 middleware 系統強大的原因。
要套用多個 store enhancer,你可以使用
compose()
。