Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

redux-thunk源码分析 #8

Open
liujie2019 opened this issue Dec 4, 2018 · 0 comments
Open

redux-thunk源码分析 #8

liujie2019 opened this issue Dec 4, 2018 · 0 comments

Comments

@liujie2019
Copy link
Owner

liujie2019 commented Dec 4, 2018

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等。

1. 使用

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;
// 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);
  };
}

thunk.withExtraArgument允许给返回的函数传入额外的参数,它比较难理解的部分和thunk一样,如下:

({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  }

我们先看看,在reudx中如何使用中间件:直接将thunk中间件引入,作为 applyMiddleware的参数,然后传入createStore方法中,就完成了 store.dispatch()的功能增强,这样就可以进行一些异步的操作了。其中 applyMiddlewareRedux的一个原生方法,将所有中间件组成一个数组,依次执行,中间件多了可以当做参数依次传进去。

let store = createStore(
    reducer,
    applyMiddleware(thunk)
);

那么createThunkMiddleware函数中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
    }
  }
}

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

let store = createStore(
    reducer,
    applyMiddleware(thunk) // 返回一个`enhancer`
);
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)
  }
}

在上述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)

上述代码中的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;

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);
        };
      }
    }
}
//  compose源码
// 当funcs长度为1时,返回funcs中的第一项对应的函数
    if (funcs.length === 1) {
        return funcs[0]
    }

从上述代码中我们可以看出,chain就是以next作为形参的匿名函数,compose函数作用是:将多个函数连接起来,将一个函数的返回值作为另一个函数的传参进行计算,得出最终的返回值。这里chain是包含一个函数的数组,根据compose的源码,我们可以知道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);
};

如果传入的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'
}

加入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. 讓你的Action能作更多 — Redux-Thunk
  2. 掌控 redux 异步
  3. React系列——redux-thunk源码分析
  4. redux-thunk
  5. redux异步操作学习笔记
  6. Redux, Redux thunk 和 React Redux 源码阅读
  7. redux-thunk 源码全方位剖析
@liujie2019 liujie2019 added this to Done in Redux Mar 3, 2019
@liujie2019 liujie2019 moved this from Redux to Redux-thunk in Redux Mar 3, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Redux
Redux-thunk
Development

No branches or pull requests

1 participant