Redux API之applyMiddleware
applyMiddleware(...middlewares)
使用包含自定义功能的 middleware 来扩展 Redux 是一种推荐的方式。Middleware 可以让你包装 store 的dispatch
方法来达到你想要的目的。同时, middleware 还拥有“可组合”这一关键特性。多个 middleware 可以被组合到一起使用,形成 middleware 链。其中,每个 middleware 都不需要关心链中它前后的 middleware 的任何信息。
Middleware 最常见的使用场景是无需引用大量代码或依赖类似 Rx 的第三方库实现异步 actions。这种方式可以让你像 dispatch 一般的 actions 那样 dispatch 异步 actions。
例如,redux-thunk 支持 dispatch function,以此让 action creator 控制反转。被 dispatch 的 function 会接收 dispatch
作为参数,并且可以异步调用它。这类的 function 就称为 thunk。另一个 middleware 的示例是 redux-promise。它支持 dispatch 一个异步的 Promise action,并且在 Promise resolve 后可以 dispatch 一个普通的 action。
Middleware 并不需要和 createStore
绑在一起使用,也不是 Redux 架构的基础组成部分,但它带来的益处让我们认为有必要在 Redux 核心中包含对它的支持。因此,虽然不同的 middleware 可能在易用性和用法上有所不同,它仍被作为扩展 dispatch
的唯一标准的方式。
参数
...middlewares
(arguments): 遵循 Redux middleware API 的函数。每个 middleware 接受Store
的dispatch
和getState
函数作为命名参数,并返回一个函数。该函数会被传入 被称为next
的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用next(action)
,或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后一个 middleware 会接受真实的 store 的dispatch
方法作为next
参数,并借此结束调用链。所以,middleware 的函数签名是({ getState, dispatch }) => next => action
。
返回值
(Function) 一个应用了 middleware 后的 store enhancer。这个 store enhancer 就是一个函数,并且需要应用到 createStore
。它会返回一个应用了 middleware 的新的 createStore
。
示例: 自定义 Logger Middleware
1 import { createStore, applyMiddleware } from 'redux' 2 import todos from './reducers' 3 4 function logger({ getState }) { 5 return (next) => (action) => { 6 console.log('will dispatch', action) 7 8 // 调用 middleware 链中下一个 middleware 的 dispatch。 9 let returnValue = next(action) 10 11 console.log('state after dispatch', getState()) 12 13 // 一般会是 action 本身,除非 14 // 后面的 middleware 修改了它。 15 return returnValue 16 } 17 } 18 19 let createStoreWithMiddleware = applyMiddleware(logger)(createStore) 20 let store = createStoreWithMiddleware(todos, [ 'Use Redux' ]) 21 22 store.dispatch({ 23 type: 'ADD_TODO', 24 text: 'Understand the middleware' 25 }) 26 // (将打印如下信息:) 27 // will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' } 28 // state after dispatch: [ 'Use Redux', 'Understand the middleware' ]
示例: 使用 Thunk Middleware 来做异步 Action
1 import { createStore, combineReducers, applyMiddleware } from 'redux' 2 import thunk from 'redux-thunk' 3 import * as reducers from './reducers' 4 5 // 调用 applyMiddleware,使用 middleware 增强 createStore: 6 let createStoreWithMiddleware = applyMiddleware(thunk)(createStore) 7 8 // 像原生 createStore 一样使用。 9 let reducer = combineReducers(reducers) 10 let store = createStoreWithMiddleware(reducer) 11 12 function fetchSecretSauce() { 13 return fetch('https://www.google.com/search?q=secret+sauce') 14 } 15 16 // 这些是你已熟悉的普通 action creator。 17 // 它们返回的 action 不需要任何 middleware 就能被 dispatch。 18 // 但是,他们只表达「事实」,并不表达「异步数据流」 19 20 function makeASandwich(forPerson, secretSauce) { 21 return { 22 type: 'MAKE_SANDWICH', 23 forPerson, 24 secretSauce 25 } 26 } 27 28 function apologize(fromPerson, toPerson, error) { 29 return { 30 type: 'APOLOGIZE', 31 fromPerson, 32 toPerson, 33 error 34 } 35 } 36 37 function withdrawMoney(amount) { 38 return { 39 type: 'WITHDRAW', 40 amount 41 } 42 } 43 44 // 即使不使用 middleware,你也可以 dispatch action: 45 store.dispatch(withdrawMoney(100)) 46 47 // 但是怎样处理异步 action 呢, 48 // 比如 API 调用,或者是路由跳转? 49 50 // 来看一下 thunk。 51 // Thunk 就是一个返回函数的函数。 52 // 下面就是一个 thunk。 53 54 function makeASandwichWithSecretSauce(forPerson) { 55 56 // 控制反转! 57 // 返回一个接收 `dispatch` 的函数。 58 // Thunk middleware 知道如何把异步的 thunk action 转为普通 action。 59 60 return function (dispatch) { 61 return fetchSecretSauce().then( 62 sauce => dispatch(makeASandwich(forPerson, sauce)), 63 error => dispatch(apologize('The Sandwich Shop', forPerson, error)) 64 ) 65 } 66 } 67 68 // Thunk middleware 可以让我们像 dispatch 普通 action 69 // 一样 dispatch 异步的 thunk action。 70 71 store.dispatch( 72 makeASandwichWithSecretSauce('Me') 73 ) 74 75 // 它甚至负责回传 thunk 被 dispatch 后返回的值, 76 // 所以可以继续串连 Promise,调用它的 .then() 方法。 77 78 store.dispatch( 79 makeASandwichWithSecretSauce('My wife') 80 ).then(() => { 81 console.log('Done!') 82 }) 83 84 // 实际上,可以写一个 dispatch 其它 action creator 里 85 // 普通 action 和异步 action 的 action creator, 86 // 而且可以使用 Promise 来控制数据流。 87 88 function makeSandwichesForEverybody() { 89 return function (dispatch, getState) { 90 if (!getState().sandwiches.isShopOpen) { 91 92 // 返回 Promise 并不是必须的,但这是一个很好的约定, 93 // 为了让调用者能够在异步的 dispatch 结果上直接调用 .then() 方法。 94 95 return Promise.resolve() 96 } 97 98 // 可以 dispatch 普通 action 对象和其它 thunk, 99 // 这样我们就可以在一个数据流中组合多个异步 action。 100 101 return dispatch( 102 makeASandwichWithSecretSauce('My Grandma') 103 ).then(() => 104 Promise.all([ 105 dispatch(makeASandwichWithSecretSauce('Me')), 106 dispatch(makeASandwichWithSecretSauce('My wife')) 107 ]) 108 ).then(() => 109 dispatch(makeASandwichWithSecretSauce('Our kids')) 110 ).then(() => 111 dispatch(getState().myMoney > 42 ? 112 withdrawMoney(42) : 113 apologize('Me', 'The Sandwich Shop') 114 ) 115 ) 116 } 117 } 118 119 // 这在服务端渲染时很有用,因为我可以等到数据 120 // 准备好后,同步的渲染应用。 121 122 import { renderToString } from 'react-dom/server' 123 124 store.dispatch( 125 makeSandwichesForEverybody() 126 ).then(() => 127 response.send(renderToString(<MyApp store={store} />)) 128 ) 129 130 // 也可以在任何导致组件的 props 变化的时刻 131 // dispatch 一个异步 thunk action。 132 133 import { connect } from 'react-redux' 134 import { Component } from 'react' 135 136 class SandwichShop extends Component { 137 componentDidMount() { 138 this.props.dispatch( 139 makeASandwichWithSecretSauce(this.props.forPerson) 140 ) 141 } 142 143 componentWillReceiveProps(nextProps) { 144 if (nextProps.forPerson !== this.props.forPerson) { 145 this.props.dispatch( 146 makeASandwichWithSecretSauce(nextProps.forPerson) 147 ) 148 } 149 } 150 151 render() { 152 return <p>{this.props.sandwiches.join('mustard')}</p> 153 } 154 } 155 156 export default connect( 157 state => ({ 158 sandwiches: state.sandwiches 159 }) 160 )(SandwichShop)
小贴士
-
Middleware 只是包装了 store 的
dispatch
方法。技术上讲,任何 middleware 能做的事情,都可能通过手动包装dispatch
调用来实现,但是放在同一个地方统一管理会使整个项目的扩展变的容易得多。 -
如果除了
applyMiddleware
,你还用了其它 store enhancer,一定要把applyMiddleware
放到组合链的前面,因为 middleware 可能会包含异步操作。比如,它应该在 redux-devtools 前面,否则 DevTools 就看不到 Promise middleware 里 dispatch 的 action 了。 -
如果你想有条件地使用 middleware,记住只 import 需要的部分:
1 let middleware = [ a, b ] 2 if (process.env.NODE_ENV !== 'production') { 3 let c = require('some-debug-middleware') 4 let d = require('another-debug-middleware') 5 middleware = [ ...middleware, c, d ] 6 } 7 const createStoreWithMiddleware = applyMiddleware(...middleware)(createStore)
-
这样做有利于打包时去掉不需要的模块,减小打包文件大小。
-
有想过
applyMiddleware
本质是什么吗?它肯定是比 middleware 还强大的扩展机制。实际上,applyMiddleware
只是被称为 Redux 最强大的扩展机制的 store enhancers 中的一个范例而已。你不太可能需要实现自己的 store enhancer。另一个 store enhancer 示例是 redux-devtools。Middleware 并没有 store enhancer 强大,但开发起来却是更容易的。 -
Middleware 听起来比实际难一些。真正理解 middleware 的唯一办法是了解现有的 middleware 是如何工作的,并尝试自己实现。需要的功能可能错综复杂,但是你会发现大部分 middleware 实际上很小,只有 10 行左右,是通过对它们的组合使用来达到最终的目的。