在上一篇中咱们了解到,更新Redux中状态的流程是这种:action -> reducer -> new state。javascript
文中也讲到。action是一个普通的javascript对象、reducer是一个普通的方法。在reducer中依据当前的state、接收到的action来生成一个新的state以达到更新状态的目的。html
那么问题来了。每次action被触发(dispatch)。reducer就会同步地对store进行更新,在实际开发项目的时候,有很是多需求都是需要经过接口等形式获取异步数据后再进行更新操做的。怎样异步地对store进行更新呢?java
仍是拿上一篇文中写的计数器来作样例,现在需要加入一个按钮。点击这个按钮的时候会在1秒以后对counter的值进行加1操做。react
最简单的方法固然是把store.dispatch({type: ‘INCREASE’})放到一个setTimeout的回调里去运行:git
document.getElementById('btn_async_increase').addEventListener('click', function () {
setTimeout(function () {
store.dispatch({type: 'INCREASE'});
}, 1000);
});
但是这事实上仍是一次同步的更新操做,并不是咱们想要的。(在dispatch以后的store更新仍是同步的)github
Redux自己提供的Api与功能并很少,但是它提供了一个中间件(插件)机制,可以使用第三方提供的中间件或本身编写一个中间件来对Redux的功能进行加强,比方可以使用redux-logger这个中间件来记录action以及每次action先后的state、使用redux-undo来取消/重作action、使用redux-persist-store来对store进行持久化等等。npm
不少其它的中间件及相关工具可以查看这里。编程
要实现异步的action。有多个现成的中间件可供选择,这里选择官方文档中使用的redux-thunk这个中间件来实现。json
首先固然需要先获取redux-thunk,在通常的项目里使用npm i redux-thunk -S
来安装到项目中,在codepen里仅仅需简单地设置一下外部js文件引入就可以了。redux
在建立store的时候。咱们将ReduxThunk使用Redux.applyMiddleware
方法进行包装后传给Redux.createStore
的第二个方法:
const {createStore, applyMiddleware} = Redux;
const store = createStore(counter, applyMiddleware(ReduxThunk.default));
原来的store.dispatch
方法仅仅能接收一个普通的action对象做为參数。当咱们加入了ReduxThunk这个中间件以后。store.dispatch
还可以接收一个方法做为參数,这种方法会接收到两个參数,第一个是dispatch。等同于store.dispatch
,第二个是getState,等同于store.getState
,也就是说。现在可以这样来触发INCREASE:
store.dispatch((dispatch, getState) => dispatch({type: 'INCREASE'}));
点击按钮一秒后运行dispatch:
<div>
<p>Count: <span id="value">0</span></p>
<button id="btn_increase">+ 1</button>
<button id="btn_async_increase">+ 1 async</button>
<button id="btn_decrease">- 1</button>
</div>
document.getElementById('btn_async_increase').addEventListener('click', function () {
store.dispatch((dispatch, getState) => {
setTimeout(() => {
dispatch({type: 'INCREASE'});
}, 1000);
});
});
看一下效果(CodePen):
依托于ReduxThunk咱们实现了异步触发action的功能,那么ReduxThunk是怎么作到的呢?
咱们来看看ReduxThunk的代码。ReduxThunk一共仅仅有一个代码文件,14行代码:
https://github.com/gaearon/redux-thunk/blob/master/src/index.js
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
咱们使用的是默认导出的thunk
,也就是createThunkMiddleware()
运行的结果。
createThunkMiddleware
方法里返回了一个奇怪的东西:
({ dispatch, getState }) => next => action => { ... };
换一种写法:
function ({dispatch, getState}) {
return function (next) {
return function (action) {
...
};
};
}
事实上就是一个多层嵌套返回函数的函数,使用箭头的写法在函数式编程中叫作柯里化。对柯里化更具体的介绍可以看一看这篇张鑫旭的博客。
第一个(最外层)方法的參数是一个包括dispatch和getState字段(方法)的对象。事实上就是store对象。因此也可以写成:
store => next => action => { ... };
这事实上就是一个Redux中间件的基本写法。
參数next是一个方法,这种方法的做用是通知下一个Redux中间件对此次的action进行处理: next(action)
,假设一个中间件中没有运行next(action)
,则action会中止向兴许的中间件传递。并阻止reducer的运行(store将不会因为本次的action而更新)。
參数action就不用多说了,就是当前被触发的action。
在ReduxThunk这个中间件中,作的处理很是easy:推断当前的action是否为一个方法。假设是,就运行action这种方法,并将store.dispatch
与store.getState
方法做为參数传递给action方法;假设不是,则运行next(action)
将控制权转移给下一个中间件(假设有)。
因此当咱们给store.dispatch
方法传入一个方法的时候。ReduxThunk就会去运行这种方法,以达到自由控制action触发流程的一个目的。
上面咱们给计数器写的异步action仅仅是为了做演示,简单地使用setTimeout来触发action。如下给出一个比較贴近现实的样例。
在某个项目中。咱们需要依据一个userId来调用后端接口获取这个用户的具体信息并存储到Redux store中:
function getUserDetail (userId) {
return (dispatch, getState) => {
if (getState().user.id === userId) {
// store中的user已经为当前的目标user,无需反复获取
return;
}
dispatch({type: 'USER_DETAIL_REQUEST', payload: userId});
fetch(`${API_ROOT}/user/${userId}`)
.then(res => res.json())
.then(res => {
// 触发SUCCESS的action后在reducer中更新user数据
dispatch({type: 'USER_DETAIL_REQUEST_SUCCESS', payload: res});
})
.catch(err => dispatch({type: 'USER_DETAIL_REQUST_FAILURE', payload: err})
};
}
// 获取userId为10000的用户详情
store.dispatch(getUserDetail(10000));