redux源码分析:applyMiddleware

redux 添加中间件会调用 applyMiddleware
更新于: 2021-12-19 12:57:28

一个简单的 log 中间件的实现(./middlewares/log.js)

export default (store) => {
  return (next) => {
    return (action) => {
      console.log("dispatch", action.type);
      let result = next(action);
      console.log("newState", store.getState());
      return result;
    };
  };
};

先看入口部分(app.js)

import logMiddleware from './middlewares/log';

//...
createStore(reducer, 10, applyMiddleware(logMiddleware))

源码部分 node_modules/redux/createStore.js

export default function createStore(reducer, preloadedState, enhancer) {
	// 省略号...
   if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

applyMiddleware 做了什么事情(参考了这里)

  • applyMiddleware(thunk)就相当于一个enhancer,它负责扩展redux,说白了就是扩展storedispatch方法。
  • 既然要改造 store,那么就得把store作为参数传递进这个enhancer中,再吐出一个改造好的store。吐出来的这个store的dispatch方法,是enhancer改造store的最终实现目标。
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    let dispatch = store.dispatch
    let chain = []

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

    return {
      ...store,
      dispatch
    }
  }
}

原理解析(改造 createStore)

  • 干的事情: 将用户的 store 利用中间件进行2次改造
  • 本质上改造的东西是:dispatch 方法,以及 dispatch 里面的内容

上面是大多数网上的结论,我有了更加明确的答案:

  • applyMiddleware本质上是改造 createStore 这个方法
  • 真正的 middleware 是改造 dispatch 方法,网上大部分的解释问题就在这里,把 applyMiddleware 方法的作用变成了 middleware 的作用了
  • 这也印证了为什么 createStore 的第3个参数名不叫: middleware,而叫 enhancer,本质上是  createStore 的增强,或者说  decorator
  • enhancer 其实也是可以组合的,combinedEnhancers 这个方法就有在官方 issue 里讨论过
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => {
    const store = createStore(reducer, preloadedState, enhancer)
    const newDispatch = compose(...chain)(store.dispatch)
    return {
      ...store,
      dispatch: newDispatch
    }
  }
}

// 如果只考虑 middleware 机制,这样就行了; 我在实际项目中改造用过,一点问题没有用
export default function applyMiddleware(...middlewares) {
  return (store) => {
    const newDispatch = compose(...chain)(store.dispatch)
    return {
      ...store,
      dispatch: newDispatch
    }
  }
}

关于 enhancer 的一段有趣的讨论

enhancer function was invoked inside createStore:
redux/src/createStore.js line 50: return enhancer(createStore)(reducer, preloadedState).
redux/src/applyMiddleware.js line 20: return (createStore) => (reducer, preloadedState, enhancer) => {
redux/src/applyMiddleware.js line 21: var store = createStore(reducer, preloadedState, enhancer)
...}

// 原始代码
return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
      var store = createStore(reducer, preloadedState, enhancer);
// 期望的代码
return function(store) {
  • 这段代码本质上还是要被类似于 compose 的东西消费
  • 所以,只能是这种 currying 的代码,因为可以延时执行
  • 直接写成 store, enhancer 就不能继续被 compose/combinedEnhancers 等类似的东西组合使用了

参考