Skip to content

redux源码分析(4) — applyMiddleware #36

Description

@dark9wesley

applyMiddleware.js

applyMiddleware帮助我们将第三方的中间件,转换成能增强store的enhancer。

下面是一个运用redux-thunk的例子:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducer';

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

export store

可以看到applyMiddleware接收中间件,执行返回的结果就是一个能增强store的enhancer。

还记得在createStore.js里有这么一段代码吗?:

if(typeof enhancer !== undefined){
  if(typeof enhancer !== 'function'){
    throw new Error('Expected the enhancer to be a function.');
  }

  return enhancer(createStore)(reducer, preloadedState)
}

可以看到,在createStore中,如果发现传入了合法的enhancer,就会将逻辑交给enhancer执行,让它返回增强后的store。

结合两段代码,我们可以推测出applyMiddleware函数的大致模样:

// 第一层,传入中间件,返回一个enhancer,供createStore使用
function applyMiddleWare(...middlewares){

  // 第二层,供createStore内部调用
  return (createStore) => {

    // 第三层,供createStore内部调用
    return (reducer, preloadedState) => {

      // ....具体逻辑

      return store; // 返回增强后的store
    }
  }
}

下面我们看一下applyMiddleware.js里的具体源码:

import { compose } from 'redux'

export default function applyMiddleware(...middlewares){
  return (createStore) => {
    return (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
      }
    }
  }
}

可以看到,这个函数做了以下几件事情:

  1. 调用传入的createStore,构建一个基础的store
  2. 定一个dispatch方法,这个方法被调用就会报错,注意这个方法在下面会被改写!
  3. 定义一个middlewareAPI对象,里面包含getState方法和会报错的dispatch方法
  4. 使用map方法,将中间件都执行一次,参数是上一步的middlewareAPI对象,得到chain数组
  5. 使用compose方法,将chian数组中的所有函数进行组合,得到一个函数,并赋值给dispatch(改写dispatch)
  6. 返回增强后的store

下面重点讲解会让人迷惑的第4和第5步。

第4步:使用map方法,将中间件都执行一次,参数是上一步的middlewareAPI对象,得到chain数组

为了方便理解,我们这里引入redux-thunk,从头开始分析,下面是redux-thunk的源码:

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

const thunk = createThunkMiddleware()

export default thunk

可以看到导出的thunk是createThunkMiddleware()执行的结果,所以我们得到的thunk是:

const thunk = function ({ dispatch, getState }) {
  return function (next) {
    return function (action)  {
      if (typeof action === 'function') {
        return action(dispatch, getState)
      }
      return next(action)
    }
  }
}

当applyMiddleware(thunk)时,applyMiddleware内部得到的middlewares就是:

middlewares = [thunk]

当到第4步,会使用map方法,将中间件都执行,并传入middlewareAPI, 也就是

const chian = [thunk].map(middleware => middleware(middlewareAPI))

这里会执行thunk, 并传入dispatch和getState,执行后,会得到这么一个函数:

const chainThunk = function (next){
  return function (action)  {
    if (typeof action === 'function') {
      return action(dispatch, getState)
    }
    return next(action)
  }
}

所以我们可以知道chain的值:

chain = [chainThunk] 

第5步:使用compose方法,将chian数组中的所有函数进行组合,得到一个函数,并赋值给dispatch(改写dispatch)

redux源码分析(3) — compose里,我们已经讲解了compose的作用。

我们将dispatch = compose(...chain)(store.dispatch)分成两部分执行

  1. 第一步
// 上一步得到的chain
chian = [chainThunk]

compose(...chain)

compose执行后,我们可以得到这么一个函数, 我们将它命名为composeThunk:

const composeThunk = (...args) => chianThunk(...args)
  1. 第二步
dispatch = composeThunk(store.dispatch)

其实注意观察,这里我们调用的composeThunk,本质上也是chianThunk, 所以实际上:

const chainThunk = function (next){
  return function (action)  {
    if (typeof action === 'function') {
      return action(dispatch, getState)
    }
    return next(action)
  }
}

dispatch = chainThunk(store.dispatch)

所以我们得到的新dispatch就是:

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

在applyMiddleware的最后:

return {
  ...store,
  dispatch
}

会返回一个被强化了dispatch的store,所以所谓的enhancer,其实就是用来强化store的dispatch的。

最后,还有需要补充的是,我们这里只举例了使用一个中间件的情况,其实大多数项目中,我们都会有多个中间件。

多个中间件的情况

假如我们有以下两个中间件:

const middleware1 = ({dispatch, getState}) => {
  return (next) => {
    return (action) => {
      ...do something
      next(action)
    }
  }
}

const middleware2 = ({dispatch, getState}) => {
  return (next) => {
    return (action) => {
      ...do something
      next(action)
    }
  }
}

我们会在第4步后,得到这么两个函数:

const chain1 = function (next) => {
  return (action) => {
      ...do something
      next(action)
  }
}

const chain2 = function (next) => {
  return (action) => {
      ...do something
      next(action)
  }
}

// chain
chain = [chain1, chain1]

当这个chain被第5步compose(...chain)(store.dispatch)调用时,实际情况如下

compose(...chain)(store.dispatch)
等价于
compose(chain1, chain2)(store.dispatch)
等价于
chain1(chain2(store.dispatch))

最后增强后的dispatch就是上方代码的执行结果,每个中间件最后返回的(action) => ...都会成为下一个中间件的next参数。

所以,当一个中间件做完事情后,就可以调用next,将action传递给下一个中间件。

最后贴一下redux-thunk的带注释代码,帮助理解中间件:

// 第一层 返回一个接受dispatch 和 getState的函数
// 这层供applyMiddleware里传入middlewareAPI调用
const thunk =  ({ dispatch, getState }) => {

  // 第二层 返回一个接受next的函数
  // 这层供compose函数调用
  // next参数是上一层的(action) => ....
  return function (next) {

    // 第三层 返回一个接受action的函数
    // 这层也就是下一个中间件的next参数
    // 当只有一个中间件时,这层就是被增强后的dispatch
    return function (action)  {

      // 如果action类型是函数的话,执行它
      if (typeof action === 'function') {
        return action(dispatch, getState)
      }

      // 不是函数的类型,交给下一个中间件执行
      return next(action)
    }
  }
}

export default thunk

结语

applyMiddleware是redux中最为复杂的一个函数了,虽然代码量不多,但是运用闭包的技巧和函数式思想让人折服。

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions