redux源码解析之applyMiddleware

前言

项目中一直使用redux来进行状态管理,对于redux中间件传递这一块一直不是很理解,只懂得使用不懂得原理,今天来分析一下applyMiddleware的源码。

加载中间件的加载

首先我们来看一下在项目中是如何加载中间件的

import { createStore, applyMiddleware } from 'redux';
imoprt thunk from 'redunx-thunk';
import todos from './reducers';

const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);
const store = createStoreWithMiddleware(todos,[ 'Use Redux' ]);

如果你用过redux就会知道,加载中间件会使用到redux提供的工具方法:applyMiddleware,将你需要使用的中间件作为参数传递给applyMiddleware,它会返回一个函数,然后再将createStore传递进,最后得到一个新的createStore;

下面我们来看一下applyMiddleware的源码(可通过gitHub获取)

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

代码非常少,20行左右,下面我们就开始分析代码吧

createStore => (...args) =>{....}

我们知道applyMiddleware返回的是一个函数,从使用的方法可以看到这里createStore,就是我们一开始传递进去的那个createStore,它又返回一个函数,这个就是上面说的createStoreWithMiddleware,简单来说,最后调用的是

(reducer,initState)=>{
  const store = createStore(reducer,initState);
}
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)

这里声明了一个变量middlewareAPI,它是一个对象,有两个方法(从这里开始非常的绕,我已经有点晕了。使用了大量的高阶函数)
1.getState 这个方法是对store中的getStat方法的引用
2.dispatch 从上面可以看到已经定义了一个dispatch,规定不允许在加载中间时调用,在下面又重新修改了指向。(表示对store.dispatch的封装)

chain = middlewares.map(middleware => middleware(middlewareAPI));

这行代码对传递进行的中间件数组进行一次map,map中调用中间件,并将middlewareAPI作为参数传递给中间件,这里我们就拿redux-thunk来看看中间件是长什么样子的

export default function thunkMiddleware({ dispatch, getState }) {
  return next => action =>
    typeof action === 'function' ?
      action(dispatch, getState) :
      next(action);
}

我们通常使用redux-thunk来进行ajax操作,在上面看到进行map操作时将middlewareAPI传递到了redux-thunk,然后它返回了一个函数,用es5表示就是下面这样子的

funciton thunkMiddleware(middlewareAPI){
  return function(next){ 
      return function(action){
         typeof action === 'function' ?
         action(dispatch, getState) :
         next(action);     
     }
  }
}
我们在回到上面
chain = middlewares.map(middleware => middleware(middlewareAPI))

那么现在我们可以知道,这个东西返回了一个像这样的函数

function(next){ 
      return function(action){
         typeof action === 'function' ?
         action(dispatch, getState) :
         next(action);     
     }
  }

在接着往下看之前

dispatch = compose(...chain)(store.dispatch);

在说这里之前,我们先看一下compose的源码

export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

之前说到chain是一个组数,那么chain === funcs,这个方法首先判断chain数组的长度,如果为0就就返回一个什么都不做的函数(为0就意味着没有中间件),如果直接一个中间件就返回这个中间件并调用它,并且将store.dispatch传递进去,并赋值给dispatch

return funcs.reduce((a, b) => (...args) => a(b(...args))

我们重点来看这行代码,reduce是数组的的一个方法,它的第一个参数是一个函数,这个函数接受4个参数,分别是previousValu(上一次的值),currentValue(当前值),currentIndex(当前值的索引),array;reduce 为数组中的每一个元素依次执行回调函数(不包括数组中被删除或从未被赋值的元素)。

这里的args就是外面传递的store.dispatch;这段代码写成es5的形式大概是下面这样(...agrs是es6语法,如果有不清楚的请参阅# ECMAScript 6 入门)

funcs.reduce(function(a,b){
  retrurn function(... args]){
    return a(b(... args))
  }
})

从代码中可以看出(注意两个...ags,代表的含义不同,第一个代表的是剩余参数,第二个agrs代表的是展开数组),对每个元素执行了回调函数,它又再次返回了一个新的函数(真的无力,嵌套太深了)

return a(b(... args))

继续来看这里,这里是依次执行了中间件,并将返回的函数传递给下一个中间件
如果有数组[fn1,fn2,fn3,fn4],那么返回 fn1(fn2(fn3(fn4(..args))))
还记得thunk的代码吗,这里我们还是拿thunk来举例,看下面代码

 return ({ dispatch, getState }) => next => action => { 
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }
    return next(action);
  };

如果只有一个中间件,那么这里的next表示的是store.dispatch,否则表示其它的中间件。
总的来说,如果我们只使用一个中间,那么最后得到的dispatch方法就是

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

推荐阅读更多精彩内容