写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。本博客的Github地址。
redux-thunk
是redux
解决异步的中间件。
当我们只使用redux
的时候,我们需要dispatch
的是一个action
对象。但是当我们使用redux-thunk
之后,我们dispatch
的是一个function
。redux-thunk
会自动调用这个function
,并且传递dispatch
方法作为其第一个参数。这样一来,我们就能在这个function
内根据我们的请求状态:开始请求,请求中,请求成功/失败,dispatch
我们期望的任何action
了,这也是为什么它能支持异步dispatch (action)
。
本质上是
redux-thunk
对store.dispatch
方法进行了增强改造,使其具备接受一个函数作为参数的能力,从而达到middleware
的效果,即在redux
的dispatch(action) => reducer => update store
这个流程中,在action
被发起之后,到达reducer
之前,加入相关操作,比如发生请求、打印log等。
使用
npm install -S redux-thunk
redux-thunk
的源码非常简洁,除去空格一共只有11
行,这11
行中如果不算上}
,则只有8
行。
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
// 如果action是一个function,就返回action(dispatch, getState, extraArgument),否则返回next(action)。
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// next为之前传入的store.dispatch,即改写前的dispatch
return next(action);
};
}
const thunk = createThunkMiddleware();
// 给thunk设置一个变量withExtraArgument,并且将createThunkMiddleware整个函数赋给它
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// thunk的内容如下
({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
}
// thunk.withExtraArgument的结果如下
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
thunk.withExtraArgument
允许给返回的函数传入额外的参数,它比较难理解的部分和thunk
一样,如下:
({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
}
2
3
4
5
6
上述代码使用函数参数的解构加上连用三个箭头函数,显得非常简洁,单同时也带来了理解的困难(这也是箭头函数的缺点之一)。把上述代码在
babel REPL
中转译为ES5
语法后,我们看到以下结果:
"use strict";
function createThunkMiddleware(extraArgument) {
return function (_ref) {
var dispatch = _ref.dispatch,
getState = _ref.getState;
return function (next) {
return function (action) {
if (typeof action === "function") {
return action(dispatch, getState, extraArgument);
}
return next();
};
};
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
这下,代码一下子我们能看懂了,不过稍等这里的dispatch,getState,next
还有action
又是什么?
我们先看看,在reudx
中如何使用中间件:直接将thunk
中间件引入,作为 applyMiddleware
的参数,然后传入createStore
方法中,就完成了 store.dispatch()
的功能增强,这样就可以进行一些异步的操作了。其中 applyMiddleware
是Redux
的一个原生方法,将所有中间件组成一个数组,依次执行,中间件多了可以当做参数依次传进去。
let store = createStore(
reducer,
applyMiddleware(thunk)
);
2
3
4
那么
dispatch,getState,next,action
这些参数是从哪里来的呢?这就需要看看applyMiddleware
的源码实现了,如下:
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
let dispatch = store.dispatch
let chain = []
// 要传给middleware的参数
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
将
thunk
作为参数传入之后,即applyMiddleware(thunk)
,返回了一个函数,这个函数其实就是一个enhancer
,然后传入redux
的createStore
函数中:
let store = createStore(
reducer,
applyMiddleware(thunk) // 返回一个`enhancer`
);
2
3
4
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
在上述
redux
源码中,createStore
函数中的enhancer
被执行,传入参数createStore
,紧接着执行了其返回的函数,传入reducer和preloadedState
。接下来,我们进入applyMiddleware和thunk
的关键部分,上面applyMiddleware
接受的最初的(…middlewares)
参数其实就是thunk
,thunk
会被执行,并且传入参数getState和dispatch
:
// 传入到thunk的参数
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// 依次执行所有的中间件(thunk)
chain = middlewares.map(middleware => middleware(middlewareAPI))
// 改写dispatch
dispatch = compose(...chain)(store.dispatch)
2
3
4
5
6
7
8
9
上述代码中的
chain
是什么呢?这就需要结合redux-thunk
源码来分析了:
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;
2
3
4
5
6
7
8
9
10
11
12
13
14
redux-thunk
中间件export default
的就是createThunkMiddleware()
函数处理之后的thunk
,再看createThunkMiddleware
这个函数,返回的是一个返回函数的函数。将上述代码编译成ES5
的代码:
function createThunkMiddleware(extraArgument) {
return function({ dispatch, getState }) {
// 这里返回的函数就是chain
return function(next) {
// 这里返回的函数就是改写的dispatch
return function(action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
从上述代码中我们可以看出,
chain
就是以next
作为形参的匿名函数,compose
函数作用是:将多个函数连接起来,将一个函数的返回值作为另一个函数的传参进行计算,得出最终的返回值。这里chain
只是一个函数,所以很简单,就是执行chain
,并将store.dispatch
作为实参传递给next
。
改造后的
dispatch
最终变为:
function(action) {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// next为之前传入的store.dispatch,即改写前的dispatch
return next(action);
};
2
3
4
5
6
7
如果传入的action是函数,则执行函数;否则直接
dispatch(action)
。
从上述分析中可以得出如下结论:
middleware
执行时传入的参数对象middlewareAPI
中确实包含getState和dispatch
两项,next
则来自dispatch = compose(...chain)(store.dispatch)
这一句中的store.dispatch
,而action
在dispatch
某个action
时传入。
一般来说,一个有效携带数据的action
是如下这样的:
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
2
3
4
加入redux-thunk
后,action
可以是函数了,依据redux-thunk
的源码,我们可以看出如果传入的action
是函数,则返回这个函数的调用。如果传入的函数是一个异步函数,我们完全可以在函数调用结束后,获取必要的数据再次触发dispatch
,由此实现异步效果。
使用场景如下:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// 注册thunk到applyMiddleware
const createStoreWithMiddleware = applyMiddleware(
thunk
)(createStore);
const store = createStoreWithMiddleware(rootReducer);
// action方法
function increment() {
return {
type: INCREMENT_COUNTER
};
}
// 执行一个异步的dispatch
function incrementAsync() {
return dispatch => {
setTimeout(() => {
dispatch(increment());
}, 1000);
};
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24