redux applyMiddleware 中间件原理

记录下自己对redux 中间件的理解,希望可以帮助到你。

applyMiddleware中间件在哪个部分运行

中间件 听名字就能感觉出来 肯定是运行在程序中间

重点是运行在哪个部分中间

redux applyMiddleware 中间件原理_第1张图片
正常流程这样子

这个一个正常的流程

中间件允许我们在dispatch action 之后 到达reducer之前 这个区间 做自己想做的事情(为所欲为≧ω≦)

redux applyMiddleware 中间件原理_第2张图片
加入了中间件这样子

现在搞明白了他的运行环境,那这个东西到底能做些什么,网上有很多例子 打印啊 报错日志啊 这些都没用过(允许我皮一下),不过异步请求是我们在项目中实实在在需要用到的

使用场景 发起一个action 自动ajax请求 请求成功后发出reducer 更改state 触发更新view 这是一个异步请求的过程

然后我们来结合源码弄清楚他的运行原理 最后再说怎么实现异步请求。

看一下实际项目中运行的代码


import { createStore, applyMiddleware, compose } from 'redux';

import rootReducer from' ../reducers';

import DevTools from' ../containers/DevTools';

import thunk from 'redux-thunk';

finalCreateStore =compose(
//这是实际项目中用到的场景(拿项目说话,有理有据的样子)
applyMiddleware(thunk, api, reduxRouterMiddleware),
//applyMiddleware里面三个参数 thunk是redux-thunk提供  api 自己封装  
//reduxRouterMiddleware可以把router中的location(url信息等)注入到redux中
 DevTools.instrument(),
//这个是react调试用的
persistState(getDebugSessionKey())
//这个是react增强调试 切换路由状态将会被清空重置
//主要介绍和中间件相关的  这些无关的简要略过
)(createStore);
const store = finalCreateStore(rootReducer, initialState);

可以看到实际项目中应用的场景是这样子
重新整理下代码 屏蔽那些 本轮不看的代码

import { createStore, applyMiddleware, compose } from 'redux';

import rootReducer from' ../reducers';
const store = compose(
  applyMiddleware(d1, d2, d3),
)(createStore)(rootReducer, initialState);

是的 现在这样是 咱们要说的主要代码
一层层看 compose先是运行一次传递createStore作为参数 内部返回的代码又运行 传递了rootReducer 和 initialState 作为参数
那现在我们来看看 compose 内部执行了什么

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */
//在这里其实已经可以看到 compose(f, g, h)  执行结果就是  (...args) => f(g(h(...args)))
function compose() {
  for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
    funcs[_key] = arguments[_key];
  }

  if (funcs.length === 0) {
    return function (arg) {
      return arg;
    };
  }

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

  return funcs.reduce(function (a, b) {
    return function () {
      return a(b.apply(undefined, arguments));
    };
  });
}

compose代码是我现在项目中用的,直接到node_modules中翻出来的
可能和以前版本不一样 网上很多文章 内部代码是用 reduceRight 不管啦以我自己翻到的为主(就是这么霸气 ╮( ̄▽  ̄)╭)

funcs.reduce(function (a, b) {
    return function () {
      return a(b.apply(undefined, arguments));
    };
  });
主要的代码可以看见 就是这些 代码不多
funcs.reduce((a, b) => (...args) => a(b(...args)))
可以简写成这样子 没错吧 (es6语法不熟练的同学自行去补补课,reduce就是一个叠加器叠加执行)
举个栗子
var fn1 = val => 'fn1-' + val
var fn2 = val => 'fn2-' + val 
var fn3 = val => 'fn3-' + val 
compose(fn1,fn2,fn3)('测试')
fn1-fn2-fn3-测试  这是执行结果
第一次执行开始
a = fn1
b = fn2 
(...args) => a(b(...args))   
(...args) = > fn1(fn2(...args))
第一步执行完 a = (...ag) => fn1(fn2(...ag))    //如果不理解怎么变成这个样子的去看看 reduce 函数
第二步开始执行
a = (...args) => fn1(fn2(...args))
b = fn3 
(...args) => a(b(...args))   
(...args) => a( fn3(...args) ) 
(...args) => fn1(fn2(fn3(...args))) 
咦咋就变成这样了  a = (...args) => fn1(fn2(...args))  执行这一步的时候 b(...args)作为了参数传进了a 也就是 ...args =  b(...args) = fn3(...args)
最后的运行结果就是 (...args) => fn1(fn2(fn3(...args))) 

总结一下compose 就是函数叠加执行内层的执行结果时外层的参数 生成过程是由内向外,执行过程是由外向内

这是咱们从源码解读、代码演示、官方注释、上都能看出来的结果。记住最后面最右面的最先执行 以后 compose 不再说了
compose 说完了
回到咱们最初的代码

import { createStore, applyMiddleware, compose } from 'redux';

import rootReducer from' ../reducers';
const store = compose(
  applyMiddleware(f1, f2, f3),
)(createStore)(rootReducer, initialState);

再看 applyMiddleware 里面到底执行了什么东东

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
    }
  }
}

也是我从node_modules里翻出来的
代码看上去要比之前解读过得compose长一点 别怕 一点点看
首先可以看到 运行 applyMiddleware 后 会返回一个函数 (高阶函数的用法)需要传递createStore作为参数 然后又返回一个函数 需要传递reducer, preloadedState, enhancer三个值作为参数
感觉比较乱我们用图参照的比较一下就清晰了


redux applyMiddleware 中间件原理_第3张图片
参数对照图

很清晰把每一个都对上了
接下来继续看
const store = createStore(reducer, preloadedState, enhancer)
创建了 store 这个对象是 createStore生成的
那再来看下 createStore 内部做了些什么 (可能你有点不习惯,我在探索技术的时候就是一层层往下屡着看,看到不会的就往下面找,直至全部搞懂)

createStore 内部

createStore内部源码就比较多了 咱们不每一行解读了(本来本文说的就挺多createStore如果再每一行过一遍文章太长了)

function createStore(reducer, preloadedState, enhancer) {
  var _ref2;

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState;
    preloadedState = undefined;
  }

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

    return enhancer(createStore)(reducer, preloadedState);
  }
  ...
}

执行 createStore需要传递三个参数reducer, preloadedState, enhancer 执行后会返回 一个对象
createStore会判断 第二个参数preloadedState是否是个函数并且enhancer 是否是undefined 如果成立
enhancer = preloadedState;
preloadedState = undefined;
然后如果enhancer 是函数 就去执行 传递的参数还是 createStore
这个不多说了 咱们没有传递 enhancer
preloadedState 正常情况就是默认值 也可以不传
执行了之后会返回一个对象

return {
    dispatch,  // 传入 action,调用 reducer 及触发 subscribe 绑定的监听函数
    subscribe, //subscribe 这个函数是用来去订阅 store 的变化
    getState,  //获取最新的State
    replaceReducer,  // 用新的 reducer 代替当前的 reducer,使用不多
    [$$observable]: observable
  }

(其实除了 dispatch 其他的你基本上用不到 dispatch 也是就几处用到,因为 bindActionCreators帮我们隐形调用了dispatch(action)这里有机会咱们以后详细说)
搞定了 createStore的返回值下面的代码就能看懂了

回头继续看applyMiddleware

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
    }
  }
}

创建了 chain 数组
创建 middlewareAPI 里面 有getState 和 dispatch
chain = middlewares.map(middleware => middleware(middlewareAPI))
先把所有的中间件 带着 middlewareAPI 运行一遍 保存到chain 中
dispatch = compose(...chain)(store.dispatch)
又看见了compose
很好 之前说了 compose 是函数叠加执行
执行刚刚 带着middlewareAPI 执行了一遍返回的函数集合chain
再传入store.dispatch作为参数 (是最后一个函数的参数呦)
代入代码咱们看一眼

const store = compose(
  applyMiddleware(f1, f2, f3),
)(createStore)(rootReducer, initialState);
//这是之前的例子
//chain是个数组 里面的内容就是每一个middleware带着middlewareAPI执行了一遍
//现在先不要管f1 f2 f3内部是如何执行的
//chain内的执行结果作为参数传入compose 在让他们叠加执行(就是把每个middleware串联起来)并赋值给dispatch
//dispatch = f1(f2(f3(store.dispatch))))  这是 compose执行后的样子
//现在可以看到 执行 dispatch 就是执行 f1(f2(f3(store.dispatch))))  也就是每个 middleware依次执行
//中间件的编写格式
const f1 = store => next => action => {
    next(action)
}
const f2 = store => next => action => {
    action = action.type + '_SUCCESS'
    next(action)
}
const f3 = store => next => action => {
    next(action)
}

三个箭头 第一次看感觉头都大
别怕在画一个图 就清晰了


redux applyMiddleware 中间件原理_第4张图片
参数对照图

这回清楚了吗
chain = middlewares.map(middleware => middleware(middlewareAPI))
middleware 的第一个参数store 是middlewareAPI 里面有getState 和 dispatch
dispatch = compose(...chain)(store.dispatch)
middleware 的第二个参数next是store.dispatch
看到这可能你就有疑问了 多么语义化的参数 next 以我小学三年级的英语水平 一眼看出这是下一个的意思 可是下一个为什么 是 store.dispatch 很不合逻辑啊
在看下 compose的函数串联
f1(f2(f3(store.dispatch))))
仔细看
f1的next是 f2
f2的next 是 f3
f3的next 是store.dispatch
之前我也强调过 store.dispatch 只是最后一个函数的参数 没有理解的同学在仔细品味品味
最后一个action参数 应该都了解 这是dispatch执行时咱们自己加的 里面有个type值对应reducer 触发更改state用的

再回到现实项目看看 (就是这样,很真实,很落地)

applyMiddleware(thunk, api, reduxRouterMiddleware)
thunk 是 redux-thunk 提供的

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 内部就是 判断你发起的action是不是函数 是函数就把dispatch, getState 作为参数传给你 不是函数就只交给下一个 next(action)
有的项目是直接把异步请求放到了actionCreator 里 这样 action是个函数 请求回来又可以触发dispacth
我所做的项目不是用的这种形式,在actionCreator中写了一个函数用于动态的创建action

import * as types from '../../constants/actions/common';

export function request(apiName, params, opts = {}) {
    return (dispatch, getState) => {
        let action = {
            'API': {
                apiName: apiName,
                params: params,
                opts: opts
            },
            type: types.API_REQUEST
        };
        return dispatch(action);
    };
}

//其他地方调用复用的方法如下:
export { request } from './request';

是在thunk后面又自己加了个中间件 api 然后再 这里可以做更多的控制 请求中的action 成功的 失败的 请求拦截的

export default store => next => action => {
    let API_OPT = action['API'];

    if (!API_OPT) {
        //我们约定这个没声明,就不是我们设计的异步action,执行下一个中间件
        return next(action);
    }
    在这里可以开启你的真正的逻辑了
    ...
};

基本上到这里就是全部内容了 讲解了 createStore, applyMiddleware, compose,thunk
我当初在看这里的时候也不是一下全能吸收的,尤其是next(action)这里
如果你的next 这里同样有和我一样的疑惑

function col(val){
    console.log(val)
    console.log('执行')
}
function f1(next){
    console.log('f1')
    return function(action){
        console.log(action,'f1Action')
        next(action/2)
    }
}

function f2(next){
    console.log('f2')
    return function(action){
        console.log(action,'f2Action')
        next(action+2)
    }
}

function f3(next){
    console.log('f3')
    return function(action){
        console.log(action,'f3Action')
        next(action+1)
    }
}

var dispatch = f1(f2(f3(col)))
dispatch(1)

运行下试试看看
在写这篇文章的时候 也找了很多材料
https://segmentfault.com/a/1190000007843340
这是我看见其中比较好的,好文要推荐,一同学习一同进步

你可能感兴趣的:(redux applyMiddleware 中间件原理)