redux applyMiddleware 中间件原理

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

applyMiddleware中间件在哪个部分运行

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

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

正常流程这样子

这个一个正常的流程

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

加入了中间件这样子

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

使用场景 发起一个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三个值作为参数
感觉比较乱我们用图参照的比较一下就清晰了


参数对照图

很清晰把每一个都对上了
接下来继续看
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)
}

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


参数对照图

这回清楚了吗
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
这是我看见其中比较好的,好文要推荐,一同学习一同进步

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352