上一讲实现了react-redux,从而能够更加优雅地在react中使用redux。javascript
可是,一个关键问题没有解决:异步操做怎么办?Action 发出之后,Reducer 当即算出 State,这叫作同步;Action 发出之后,过一段时间再执行 Reducer,这就是异步。vue
怎么才能 Reducer 在异步操做结束后自动执行呢?这就要用到新的工具:中间件(middleware)。java
为了理解中间件,让咱们站在框架做者的角度思考问题:若是要添加功能,你会在哪一个环节添加?react
只有发送 Action 的这个步骤,即store.dispatch()方法,能够添加功能。举例来讲,要添加日志功能,把 Action 和 State 打印出来,能够对store.dispatch进行以下改造。git
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 这两步之间,添加了其余功能。github
在编写中间件以前,咱们先看下在真正的redux里面是如何使用中间件的,当使用单个中间件时代码以下,其中thunk就是一个中间件:redux
const store = createStore(counter, applyMiddleware(thunk))
复制代码
有上面的代码能够看出,咱们须要作三件事情bash
第一步 对createStore函数进行扩展,使其可以接收第二个参数——中间件app
第二步 定义applyMiddleware函数,使其可以将一个中间件加入到redux中框架
第三步 实现一个中间件——redux-thunk
代码以下,检测是否有加强器,若存在则先用加强器对createStore进行扩展加强
export function createStore(reducer, enhancer) {
// 若是存在加强器,则先用加强器对createStore进行扩展加强
if (enhancer) {
return enhancer(createStore)(reducer)
}
...
}
复制代码
根据上面的代码,咱们能够知道,applyMiddleware(中间件)
返回的是一个高阶函数,接收参数createStore后,返回一个函数,而后再接收参数reducer。 所以对应代码以下:
export function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// 第一步 得到原生store以及原生dispatch
const store = createStore(...args)
let dispatch = store.dispatch
const midApi = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
// 第二步 将原生dipatch传入中间件进行扩展加强,生成新的dispatch
dispatch = middleware(midApi)(dispatch)
return {
...store, // 原生store
dispatch, // 加强扩展后的dispatch
}
}
}
复制代码
在上述代码中,咱们先是得到原生store以及原生dispatch,组成midApi,即中间件API,而后将其传入中间件,执行中间件内定义的操做,返回一个函数,再传入原生dispatch,再返回一个加强后的dispatch,最后传入action。加强后的dispatch以下:
dispatch(action) = middleware(midApi)(store.dispatch)(action)
复制代码
异步操做至少要送出两个 Action:用户触发第一个 Action,这个跟同步操做同样,没有问题;如何才能在操做结束时,系统自动送出第二个 Action 呢?
奥妙就在 Action Creator 之中。
// action creator
export function addGun() {
return {type: ADD_GUN}
}
export function addGunAsync() {
return dispatch => {
setTimeout(() => {
dispatch(addGun())
}, 2000)
}
}
复制代码
上文中有两个需求,第一个需求是store.dispatch
一个action对象(即{type: ADD_GUN}),而后当即加机枪,即:
addGun = () => store.dispatch(addGun())
addGun()
复制代码
第二个需求是store.dispatch
一个函数,这个函数内部执行异步操做,在2000ms以后再执行store.dispatch(addGun())
,加机枪,可是store.dispatch
参数只能是action这样的对象,而不能是函数。store.dispatch
的有关源码以下:
function dispatch(action) {
// reducer根据老的state和action计算新的state
currentState = reducer(currentState, action)
// 当全局状态变化时,执行传入的监听函数
currentListeners.forEach(v => v())
return action
}
复制代码
为了可以让让store.dispatch可以接收函数,咱们可使用redux-thunk,改造store.dispatch
,使得后者能够接受函数做为参数。
所以,异步操做的一种解决方案就是,写出一个返回函数的 Action Creator,而后使用redux-thunk中间件改造store.dispatch。
改造后的dispatch处理addGunAsync函数生成的action(一个函数):
// action creator
export function buyHouse() {
return {type: BUY_HOUSE}
}
function buyHouseAsync() {
return dispatch => {
setTimeout(() => {
dispatch(buyHouse())
}, 2000)
}
}
dispatch(buyHouseAsync()) = middleware(midApi)(store.dispatch)(buyHouseAsync())
复制代码
所以redux-thunk对应代码以下:
const thunk = ({dispatch, getState}) => next => action => {
// next为原生的dispatch
// 若是action是函数,执行一下,参数是dispatch和getState
if (typeof action === 'function') {
return action(dispatch, getState)
}
// 默认直接用原生dispatch发出action,什么都不作
return next(action)
}
复制代码
即判断action若是是一个函数,则执行这个函数。不然直接用原生dispatch发出action,什么都不作
这样咱们就能够经过redux-thunk中间件,实现了加强版的dispatch能够接收函数做为参数,而咱们在函数里面进行异步操做,异步操做完成后用原生dispatch发出action,从而实现了redux的异步操做全局状态的功能。
另外最近正在写一个编译 Vue 代码到 React 代码的转换器,欢迎你们查阅。