其他分享
首页 > 其他分享> > React学习笔记——redux里中间件Middleware的运行机理

React学习笔记——redux里中间件Middleware的运行机理

作者:互联网

1、前言

上篇文章中,我们详细介绍了redux的相关知识和如何使用,最后使用中间件Middleware来帮助我们完成异步操作,如下图
在这里插入图片描述
在这里插入图片描述上面是很典型的一次 redux 的数据流的过程,在增加了 middleware 后,我们就可以在这途中对 action 进行截获,并进行改变,进行其他操作。

同时,在使用 middleware 时,我们可以通过串联不同的 middleware 来满足日常的开发,每一个 middleware 都可以处理一个相对独立的业务需求且相互串联。

如上图所示,派发给 redux Store 的 action 对象,会被 Store 上的多个中间件依次处理,如果把 action 和当前的 state 交给 reducer 处理的过程看做默认存在的中间件,那么其实所有的对 action 的处理都可以有中间件组成的。值得注意的是这些中间件会按照指定的顺序一次处理传入的 action,只有排在前面的中间件完成任务之后,后面的中间件才有机会继续处理 action,同样的,每个中间件都有自己的“熔断”处理,当它认为这个 action 不需要后面的中间件进行处理时,后面的中间件也就不能再对这个 action 进行处理了

下面我们来研究研究Middleware。

2、正文

2.1、redux-thunk源码

我们以redux-thunk为例,从node_modules文件夹下面找到redux-thunk文件夹,查看其源码(下图为redux-thunk源码,一共12行)

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;

可以看出,thunk是createThunkMiddleware()运行的结果,而该函数里面还包裹了3层函数(柯里化),函数一层一层向下执行。

我们将其中的ES6的箭头函数换成普通函数,再观察

function createThunkMiddleware (extraArgument){
     // 第一层
      /*  getState 可以返回最新的应用 store 数据   */
    return function ({dispatch, getState}){
       // 第二层
         /* next 表示执行后续的中间件,中间件有可能有多个 */
        return function (next){
            // 第三层
              /*中间件处理函数,参数为当前执行的 action */
            return function (action){
                if (typeof action === 'function'){
                    return action(dispatch, getState, extraArgument);
                }
                return next(action);
            };
        }
    }
}
let thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
const store = createStore(reducer, applyMiddleware(thunk.withExtraArgument({api, whatever})));
const store = createStore(reducer, applyMiddleware(thunk));

2.2、ApplyMiddleware源码

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.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args),
    }
     // 1、将store对象的基本方法传递给中间件并依次调用中间件
    const chain = middlewares.map((middleware) => middleware(middlewareAPI))
    // 2、改变dispatch指向,并将最初的dispatch传递给compose
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch,
    }
  }
}

同样,我们将applyMiddleware的ES6箭头函数形式转换成ES5普通函数的形式

function applyMiddleware (...middlewares){
    return function (createStore){
        return function (reducer, preloadedState, enhancer){
            const store = createStore(reducer, preloadedState, enhancer);
            let dispatch = function (){
                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: (...args) => dispatch(...args)
            };
			// 1、将store对象的基本方法传递给中间件并依次调用中间件
            const chain = middlewares.map(middleware => middleware(middlewareAPI));
			// 2、改变dispatch指向,并将最初的dispatch传递给compose
            dispatch = compose(...chain)(store.dispatch);

            return {
                ...store,
                dispatch
            };
        }
    }
}

从其源码可以看出,applyMiddleware内部一开始也是两层柯里化,所以我们看看和applyMiddleware最有关系的createStore的主要源码。

2.3、CreateStore源码

在平时业务中,我们创建store时,一般这样写

const store = createStore(reducer,initial_state,applyMiddleware(···));

或者

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

所以我们也要关注createStoreapplyMiddleware的源码

createStore部分源码:

// 摘至createStore
export function createStore(reducer, preloadedState, enhancer) {
    ...
    if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
          throw new Error('Expected the enhancer to be a function.')
        }
    /*
        若使用中间件,这里 enhancer 即为 applyMiddleware()
        若有enhance,直接返回一个增强的createStore方法,可以类比成react的高阶函数
    */
    return enhancer(createStore)(reducer, preloadedState)
  }
  ......
  ......
  dispatch({ type: ActionTypes.INIT })
  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable,
  }
}

对于createStore的源码我们只需要关注和applyMiddleware有关的地方。从其内部前面一部分代码来看,其实很简单,就是对调用createStore时传入的参数进行一个判断,并对参数做矫正,再决定以哪种方式来执行后续代码。据此可以得出createStore有多种使用方法,根据第一段参数判断规则,我们可以得出createStore的两种使用方式:

const store = createStore(reducer, {a: 1, b: 2}, applyMiddleware(...));

以及

const store = createStore(reducer, applyMiddleware(...));
enhancer(createStore)(reducer, preloadedState);

实际上等同于:

applyMiddleware(mdw1, mdw2, mdw3)(createStore)(reducer, preloadedState);

这也解释了为啥applyMiddleware会有两层柯里化,同时表明它还有一种很函数式编程的用法,即 :

const store = applyMiddleware(mdw1, mdw2, mdw3)(createStore);

这种方式将创建store的步骤完全放在了applyMiddleware内部,并在其内第二层柯里化的函数内执行创建store的过程即调用createStore,调用后程序将跳转至createStore走参数判断流程最后再创建store

无论哪一种执行createStore的方式,我们都终将得到store,也就是在creaeStore内部最后返回的那个包含dispatchsubscribegetState等方法的对象。

2.4、回看ApplyMiddleware源码

对于applyMiddleware开头的两层柯里化的出现原因以及和createStore有关的方面,在前面分析过。同时,我们之前在redux-thunk里的第一层柯里化中猜测传入的对象是一个类似于store的对象,通过上个章节中applyMiddleware的确实可以确认了。

这里我们主要讨论中间件是如何通过applyMiddleware的工作起来并实现挨个串联的。

接下来这几段代码是整个applyMiddleware的核心部分,也解释了在第二章节中,我们对thunk中间件为啥有三层柯里化的疑虑

// ...
// 1、将store对象的基本方法传递给中间件并依次调用中间件
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// 2、改变dispatch指向,并将最初的dispatch传递给compose
dispatch = compose(...chain)(store.dispatch);

return {
    ...store,
    dispatch
};
// ...
2.4.1、redux-thunk的第一层柯里化

这个dispatch方法是怎么来的呢?我们来看头两行代码,这两行代码也是所有中间件被串联起来的核心部分实现,它们也决定了中间件内部为啥会有我们在之前章节中提到的三层柯里化的固定格式,先看第一行代码:

const chain = middlewares.map(middleware => middleware(middlewareAPI));
2.4.2、redux-thunk的第二层柯里化

再来看第二句代码:

dispatch = compose(...chain)(store.dispatch);
2.4.3、redux-thunk的第三层柯里化

thunk的第三层柯里化函数,即为被thunk改造后的dispatch方法:

// ...
return function (action){
    // thunk的内部逻辑
    if (typeof action === 'function'){
        return action(dispatch, getState, extraArgument);
    }
    // 调用经下一个中间件(在compose中为之前的中间件)改造后的dispatch方法(本层洋葱壳的下一层),并传入action
    return next(action);
};
// ...
2.4.4、总结

经上述分析,我们可以得出一个中间件的串联和执行时的流程,以下面这段使用applyMiddleware的代码为例:

export default createStore(reducer, applyMiddleware(middleware1, middleware2, middleware3));
// ...
return {
   ...store,
   dispatch
};
// ...

在这里插入图片描述

如上图所示:

2.5、总体流程

进过上述分析,我们可以将其主要功能按步骤划分如下:

1、依次执行middleware:

middleware执行后返回的函数合并到一个chain数组,这里我们有必要看看标准middleware的定义格式,如下

**加粗样式**export default store => next => action => {}

// 即
function (store) {
    return function(next) {
        return function (action) {
            return {}
        }
    }
}

那么此时合并的chain结构如下

[    ...,
    function(next) {
        return function (action) {
            return {}
        }
    }
]

2、改变dispatch指向:

想必你也注意到了compose函数,compose函数如下:

[...chain].reduce((a, b) => (...args) => a(b(...args)))

实际就是一个柯里化函数,即将所有的middleware合并成一个middleware,并在最后一个middleware中传入当前的dispatch

// 假设chain如下:
chain = [
    a: next => action => { console.log('第1层中间件') return next(action) }
    b: next => action => { console.log('第2层中间件') return next(action) }
    c: next => action => { console.log('根dispatch') return next(action) }
]

调用compose(...chain)(store.dispatch)后返回a(b(c(dispatch)))

可以发现已经将所有middleware串联起来了,并同时修改了dispatch的指向。最后看一下这时候compose执行返回,如下:

dispatch = a(b(c(dispatch)))

调用dispatch(action),执行循序:

   1. 调用 a(b(c(dispatch)))(action) __print__: 第1层中间件
   2. 返回 a: next(action) 即b(c(dispatch))(action)
   3. 调用 b(c(dispatch))(action) __print__: 第2层中间件
   4. 返回 b: next(action) 即c(dispatch)(action)
   5. 调用 c(dispatch)(action) __print__: 根dispatch
   6. 返回 c: next(action) 即dispatch(action)
   7. 调用 dispatch(action)

本博客参考文章:

标签:Middleware,applyMiddleware,中间件,dispatch,React,action,createStore,store
来源: https://blog.csdn.net/MRlaochen/article/details/120023180