Redux异步Action与Middleware

本文主要对于redux官方文档中的async示例结合一些源码做出一些理解。

此示例通过<select>选择相应的value(reactjs与frontend)作为selectedReddit,向Reddit API请求相应的json数据,获取数据后渲染到列表中。主要采用了react-thunk作为middleware来实现。

首先,再来看一下createStore的定义,第三个参数applyMiddleware相应的中间件,这里thunkMiddleware主要是用来异步控制的,后面会再次讲解

1
2
3
4
5
6
// createStore(reducer, initialState, enhancer)
const store = createStore(
rootReducer,
initialState,
applyMiddleware(thunkMiddleware, createLogger())
)

我们来看一下createStore的源码,是如何处理middleware的,省去了一些无用的代码,即applyMiddleware(createStore)(reducer, initialState)

1
2
3
4
5
6
7
8
export default function createStore(reducer, initialState, enhancer) {
...
if (typeof enhancer !== 'undefined') {
...
return enhancer(createStore)(reducer, initialState)
}
...
}

再看一眼applyMiddleware的代码,如果对=>有些茫然,可以先查阅ES6中箭头函数的用法。传入createStore构建store,此时因为无参数输入,enhancer为undefined。对所有middleware进行遍历,注入middlewareAPI,返回chain,即[f1(next), f2(next), f3(next), …],用compose处理chain,主要目的是将middleware组合串联,f1(f2(f3(store.dispatch)))。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, initialState, enhancer) => {
// enhancer == undefined
var store = createStore(reducer, initialState, 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)
// 结合compose的实现, 实际为 next1(next2(next3(store.dispatch)))(action)
return {
...store,
dispatch
}
}
}

compose的实现主要是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
export default function compose(...funcs) {
// ...args为store.dispatch
return (...args) => {
if (funcs.length === 0) return args[0];
// 取funcs中最后一个元素
const last = funcs[funcs.length - 1]
// funcs除去最后一个元素
const rest = funcs.slice(0, -1)
// last(..args) 为 initialValue,composed为先前值,next为store.dispatch
return rest.reduceRight((composed, f) => f(composed), last(...args))
// return next1(next2(next3(store.dispatch)))
}
}

而对于thunkMiddleware而言,传入参数{dispatch, getState}即为middlewareAPI,这里使用到了闭包,对于f1(f2(f3(store.dispatch)))而言,它的next为f2(f3(store.dispatch)),传入相应action后,会进行一个判断,若action为function则action(dispatch, getState),不然会利用next传给下一个middleware。

注意: 这里判断action是否为function的主要原因是部分action不需要到达store.dispatch,thunkMiddleware会对其进行拦截

1
2
3
4
5
6
function thunkMiddleware({ dispatch, getState }) {
return next => action =>
typeof action === 'function' ?
action(dispatch, getState) :
next(action);
}

让我们再回到示例的核心代码部分,这里尽量用少的代码来表达我的理解,具体的代码可自行研读。

(1) 当<select>从reactjs切换到frontend时,触发onChange,dispatch(selectReddit(nextReddit)),新的state形成,其中selectedReddit值变为frontend。

(2) 由于state的改变,触发componentWillReceiveProps(nextProps),发生在render前,这里会对nextProps与当前props中的selectedReddit进行比对,不同会触发dispatch(fetchPostsIfNeeded(selectedReddit)),即向服务器请求相应的json。

(3) fetchPostsIfNeeded是一个作为action的function,dispatch(fetchPostsIfNeeded(selectedReddit))fetchPostsIfNeeded(selectedReddit)会返回一个匿名函数,function(dispatch, getState)即thunkMiddleware中的action,thunkMiddleware会向这个匿名function传入dispatch与getState。

1
2
3
4
5
6
7
export function fetchPostsIfNeeded(reddit) {
return (dispatch, getState) => {
if (shouldFetchPosts(getState(), reddit)) {
return dispatch(fetchPosts(reddit))
}
}
}

(4)之后再执行相应的dispatch requestPosts与receivePosts操作。

相关参考

redux middleware 详解

官方文档-异步数据流