浅析redux源码之applyMiddleware

115 阅读3分钟

简介

中间件可以扩展redux的功能,比如最常见的 redux-thunk,该中间件使得redux支持异步action。

标准的redux中间件是一个这样的函数。

function myMiidleware({ getState,dispatch }) {
  return next => action => {
   ...
    const returnValue = next(action)
    return returnValue
  }
}

源码分析

function applyMiddleware(...middlewares){
  return (createStore) =>(reducer,preloadedState) => {
    const store = createStore(reducer, preloadedState)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action, ...args) => dispatch(action, ...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

applyMiddleware的返回结果是一个函数,该函数就是一个增强器enhancer,它会在createStore中被调用。

来看看最终的函数执行过程。

首先执行createStore得到store对象,接着又定义了dispatch函数和middlewareAPI对象,由middlewareAPI对象就可以知道为什么中间件的参数是{getState,dispatch} 。遍历中间件数组得到中间件链chain。这个chain大概长这样

chain = [
  next => action => {
    const returnValue = next(action)
    return returnValue
  },
  next => action => {
    const returnValue = next(action)
    return returnValue
  },
  next => action => {
    const returnValue = next(action)
    return returnValue
  },
  ...
]

接下来是最重要的一步,调用compose函数,得到最终的dispatch函数。

dispatch = compose(...chain)(store.dispatch)

理解了compose函数,就明白了为什么中间件可以链式调用。以下为compose源码

function compose(...funcs) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return (arg) => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

参数个数为0或1的时候好理解,主要看参数个数大于1的情况。此时compose调用funcs.reduce((a, b) => (...args) => a(b(...args)))并返回。

const composedFunc = compose(middleware1,middleware2,middleware3);
//composedFunc = (...args)=>a(b(...args));

其实composedFunc = compose(middleware1,middleware2,middleware3)就等同于 composedFunc = (...args) => middleware1(middleware2(middleware3(args)))

下面通过一个例子看看dispatch = compose(...chain)(store.dispatch)发生了什么,以及为什么调用dispatch会链式调用所有中间件。

function compose(...funcs) {
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

function middleware1(next) 
{
    console.log('middleware1-next',next);
    return action => {
      console.log('middleware2 action',action);
      return next(action);
    }
}
function middleware2(next) 
{
    console.log('middleware2-next',next);
    return action => {
      console.log('middleware2 action',action);
      return next(action);
    }
}
function middleware3(next) 
{
    console.log('middleware3-next',next);
    return action => {
      console.log('middleware3 action',action);
      return next(action);
    }
}

function dispatch(action){
  console.log('dispatch')
  return action
}
//相当于composedFunc = (...args) => middleware1(middleware2(middleware3(args)))
const composedFunc = compose(middleware1,middleware2,middleware3);

运行这段代码是不会打印任何输出的,因为compose只是根据传入的函数生成一个组合之后的函数。 在这段代码的最后增加const _dispatch = composedFunc(dispatch),再运行,则打印的信息如下

middleware3-next ƒ dispatch(action){
  console.log('dispatch')
  return action
}
middleware2-next action => {
  console.log('middleware3 action',action);
  return next(action);
}
middleware1-next action => {
  console.log('middleware2 action',action);
  return next(action);
}

需要注意的是,只有最后一个中间件的next参数是dispatch函数,其他中间件的next参数是前一个中间件的返回值。正如redux官方文档里说的

The last middleware in the chain will receive the real store's dispatch method as the next parameter, thus ending the chain.

我们得到的_dispatch函数签名是这样的

const _dispatch = action => {
  console.log('middleware1 action',action);
  return next(action);
}

_dispatch可以看作middleware1的执行的结果,但是_dispatch已经通过next将每个中间件串联了起来,所以当我们调用_dispatch({type:'test'})的时候回看到如下结果

"middleware1 action" Object { type: "test" }
"middleware2 action" Object { type: "test" }
"middleware3 action" Object { type: "test" }
"dispatch"

可以看出,_dispatch会按顺序调用每个中间件,最后调用dispatch

如果想中断中间件链,只要在某些情况下不调用next即可,比如redux-thunk的做法,当action是函数的时候不调用next

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;