理解Redux底层原理从applyMiddleware开始

题外话:年前最后一个工作日事情不多,正好把近期看过的但是容易忘的重要知识点总结一下,毕竟好记性不如烂笔头,不再少年不扶不行。

先放一个Redux中文文档关于Middleware(中间件)的官方解释。

Redux Middleware

Middleware这个概念是Redux从其他框架借鉴过来的,本意如下:

middleware是指可以被嵌入在框架接收请求到产生响应过程之中的代码。例如,Express 或者 Koa 的 middleware 可以完成添加 CORS headers、记录日志、内容压缩等工作。

而在Redux中:

middleware被用于解决不同的问题,但其中的概念是类似的。它提供的是位于 action 被发起之后,到达 reducer 之前的扩展点。 你可以利用 Redux middleware 来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。

以上两段引用均来自官方文档。 可以看出Redux中的middleware和其他框架中要做的事情大同小异。本质上是切面编程 Aspect Oriented Programming(AOP)的一种思想。业务逻辑和其他部分(日志,错误报告等)可以实现一个良好的解藕。从设计模式上来说可以视作装饰者模式

用法

let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, initialState);

下面是applyMiddleware的源码

import compose from './compose'
export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        `Dispatching while constructing your middleware is not allowed. ` +
          `Other middleware would not be applied to this dispatch.`
      )
    }
    let chain = []

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

    return {
      ...store,
      dispatch
    }
  }
}

下面是一个middleware示例的代码(来自官方文档):

/**
 * 记录所有被发起的 action 以及产生的新的 state。
 */
const logger = store => next => action => {
  console.group(action.type)
  console.info('dispatching', action)
  let result = next(action)
  console.log('next state', store.getState())
  console.groupEnd(action.type)
  return result
}

我在看源代码的时候主要提了四个问题:

1. 为什么要传入createStore
2. …args是什么
3. compose的作用
4. middleware这么多函数嵌套,每次传入的参数是什么以及作用

通过对以上问题的一一解答,加深了我对applyMiddleware源码的理解。

为什么要传入createStore

createStore

通过以上这一句可以看出就是为了单纯的创建store只不过是通过middleware把它包裹在洋葱的最深处。

…args是什么

createStore args

我把args展开,可以看出它就是Redux原生createStore方法的标准参数,这也从另一方面回答了第一个问题。

compose的作用

compose本身并非Redux特有的概念,而是函数式编程的一种思想。关于函数式编程的入门可以参考阮大神的旧作:函数式编程初探。我在这里只谈compose.

compose顾英文名思中国义即组合函数,将多个函数组合起来串联执行,一个函数的输出作为另一个函数的输入,一旦第一个函数开始执行,过程就会像多米诺骨牌。

注:当初对compose有个小小的疑惑。为什么compose中的函数参数是从右向左执行。
根据以下代码可以看出。看出啥我也很难描述,懂的自然懂(参数排列符合阅读顺序,而执行顺序恰好相反)。

let result = compose(f1, f2, f3, f4)(value);
// 等价于
let result = f1(f2(f3(f4(value))));

middleware这么多函数嵌套,每次传入的参数是什么以及作用

将applyMiddleware和middleware示例做一个对比:
applyMiddleware

middlewareExpample

从上图可以得出:
1. middlewareAPI其实就是一个暴漏了getState和dispach方法的简约版store。
2.在middleware中,next(action)是来派发action的,合理怀疑就是store的dispatch,通过对比applyMiddleware中的代码可以证实这一点。

为了印象深刻,我的路径是先假设有一个middleware,再扩展至多个:

一个middlwware

applyMiddleware

//源码中
dispatch = compose(...chain)(store.dispatch) 
//等价于
fnMiddle = fn(middlewareAPI);
dispatch = fnMiddle(store.dispatch)
//等价于
dispatch = fn(middlewareAPI)(store.dispatch)
//常用的
store.dispatch(action) 
//等价于
fn(middlewareAPI)(store.dispatch)(action)
//正好对应middleware的三个参数store, next, action

多个middlwware: fn1, fn2

//源码中
dispatch = compose(...chain)(store.dispatch) 
//等价于
dispatch= fn1Middle(fn2Middle(store.dispatch))
// fn1Middle = fn1(middlewareAPI) etc..

读到这里就有点意思了, 首先执行的是fn2Middle(store.dispatch),返回结果是如下的一个函数。紧接着fn1Middle( (action) => {} ),这个action函数相当于占据了fn1中next参数的位置。在收到action参数之前呢函数并不会执行。那么在收到acton参数之后呢,继续用语言描述实在是太难了,我试着画了一个图,希望在回看时不要再烧很多脑。

// fn2Middle(store.dispatch)执行后
(action) => {
    ....
    next(action)
    ....
}

applyMiddleware

再补充一点:

let newStore = applyMiddleware(mid1, mid2, mid3, ...)(createStore)(reducer, initialState);

插入中间件后形成了newStore。此时newStore.dispatch方法不再是原生的redux store.dispatch了,而是依次放入了中间件的逻辑,原生store.dispatch放入了洋葱模型的最里面。

dispatch= fn1Middle(fn2Middle(store.dispatch))

上述实现的关键在于applyMiddleware源码最后的return值,这个ES6风格的写法的结果就是后加入的dispatch方法覆盖了原生store.dispatch

export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    ....
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

重新梳理一遍清楚多了,这么烧脑目测过半个月准忘,记录的意义就像redux-devtool中的时光旅行一样,在纷纷扰扰的操作之后还可以找回这片初心。

祝自己以及所有人春节快乐!

猜你喜欢

转载自blog.csdn.net/Napoleonxxx/article/details/79321062