上一篇文章,我介绍了 Redux 的基本做法:用户发出 Action,Reducer 函数算出新的 State,View 重新渲染。
但是,一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。
怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)。
一、中间件的概念
为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加?
(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。
想来想去,只有发送 Action 的这个步骤,即store.dispatch()
方法,可以添加功能。举例来说,要添加日志功能,把 Action 和 State 打印出来,可以对store.dispatch
进行如下改造。
123456 let next = store.dispatch;store.dispatch = function dispatchAndLog(action) {console.log('dispatching', action);next(action);console.log('next state', store.getState());}
上面代码中,对store.dispatch
进行了重定义,在发送 Action 前后添加了打印功能。这就是中间件的雏形。
中间件就是一个函数,对store.dispatch
方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。
二、中间件的用法
本教程不涉及如何编写中间件,因为常用的中间件都有现成的,只要引用别人写好的模块即可。比如,上一节的日志中间件,就有现成的redux-logger模块。这里只介绍怎么使用中间件。
12345678 import { applyMiddleware, createStore } from 'redux';import createLogger from 'redux-logger';const logger = createLogger();const store = createStore(reducer,applyMiddleware(logger));
上面代码中,redux-logger
提供一个生成器createLogger
,可以生成日志中间件logger
。然后,将它放在applyMiddleware
方法之中,传入createStore
方法,就完成了store.dispatch()
的功能增强。
这里有两点需要注意:
(1)createStore
方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware
就是第三个参数了。
12345 const store = createStore(reducer,initial_state,applyMiddleware(logger));
(2)中间件的次序有讲究。
1234 const store = createStore(reducer,applyMiddleware(thunk, promise, logger));
上面代码中,applyMiddleware
方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger
就一定要放在最后,否则输出结果会不正确。
三、applyMiddlewares()
看到这里,你可能会问,applyMiddlewares
这个方法到底是干什么的?
它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。下面是它的源码。
12345678910111213141516 export default function applyMiddleware(...middlewares) {return (createStore) => (reducer, preloadedState, enhancer) => {var store = createStore(reducer, preloadedState, enhancer);var dispatch = store.dispatch;var chain = [];var middlewareAPI = {getState: store.getState,dispatch: (action) => dispatch(action)};chain = middlewares.map(middleware => middleware(middlewareAPI));dispatch = compose(...chain)(store.dispatch);return {...store, dispatch}}}
上面代码中,所有中间件被放进了一个数组chain
,然后嵌套执行,最后执行store.dispatch
。可以看到,中间件内部(middlewareAPI
)可以拿到getState
和dispatch
这两个方法。
四、异步操作的基本思路
理解了中间件以后,就可以处理异步操作了。
同步操作只要发出一种 Action 即可,异步操作的差别是它要发出三种 Action。
- 操作发起时的 Action
- 操作成功时的 Action
- 操作失败时的 Action
以向服务器取出数据为例,三种 Action 可以有两种不同的写法。