写作不易,Star是最大鼓励,感觉写的不错的可以给个Star⭐,请多多指教。本博客的Github地址

redux-thunkredux解决异步的中间件。

当我们只使用redux的时候,我们需要dispatch的是一个action对象。但是当我们使用redux-thunk之后,我们dispatch的是一个functionredux-thunk会自动调用这个function,并且传递dispatch方法作为其第一个参数。这样一来,我们就能在这个function内根据我们的请求状态:开始请求,请求中,请求成功/失败,dispatch我们期望的任何action了,这也是为什么它能支持异步dispatch (action)

本质上是redux-thunkstore.dispatch方法进行了增强改造,使其具备接受一个函数作为参数的能力,从而达到middleware的效果,即在reduxdispatch(action) => reducer => update store这个流程中,在action被发起之后,到达reducer之前,加入相关操作,比如发生请求、打印log等。

使用

npm install -S redux-thunk
1

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;
1
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);
  };
}
1
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);
  }
1
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();
      };
    };
  };
}
1
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()的功能增强,这样就可以进行一些异步的操作了。其中 applyMiddlewareRedux的一个原生方法,将所有中间件组成一个数组,依次执行,中间件多了可以当做参数依次传进去。

let store = createStore(
    reducer,
    applyMiddleware(thunk)
);
1
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
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

thunk作为参数传入之后,即applyMiddleware(thunk),返回了一个函数,这个函数其实就是一个enhancer,然后传入reduxcreateStore函数中:

let store = createStore(
    reducer,
    applyMiddleware(thunk) // 返回一个`enhancer`
);
1
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)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

在上述redux源码中,createStore函数中的enhancer被执行,传入参数 createStore,紧接着执行了其返回的函数,传入reducer和preloadedState。接下来,我们进入applyMiddleware和thunk的关键部分,上面applyMiddleware接受的最初的(…middlewares)参数其实就是thunkthunk会被执行,并且传入参数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)
1
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;
1
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);
        };
      }
    }
}
1
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);
};
1
2
3
4
5
6
7

如果传入的action是函数,则执行函数;否则直接dispatch(action)

从上述分析中可以得出如下结论:middleware执行时传入的参数对象middlewareAPI中确实包含getState和dispatch两项,next则来自dispatch = compose(...chain)(store.dispatch)这一句中的store.dispatch,而actiondispatch某个action时传入。

一般来说,一个有效携带数据的action是如下这样的:

{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}
1
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);
  };
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

参考文档

  1. 讓你的Action能作更多 — Redux-Thunk
  2. 掌控 redux 异步
  3. React系列——redux-thunk源码分析
  4. redux-thunk
  5. redux异步操作学习笔记
  6. Redux, Redux thunk 和 React Redux 源码阅读
  7. redux-thunk 源码全方位剖析