redux middleware 理解中

redux@4.0.0

const logger = ({ dispatch, getState }) => next => action => {
  console.log("logger will dispatch");
  // 调用 middleware 链中下一个 middleware 的 dispatch。
  let returnValue = next(action);
  console.log("logger state after dispatch");
  return returnValue;
};

/*  最后面的函数是自己的dispatch
 next是别人的dispatch
 next就是包装后的dispatch就是个函数,然后返回一个新的dispatch函数跟高阶组件一
 样,一个是函数接收一个组件返回包装后的新组件,这边中间件就是接收个函数返回个新函数 */

const thunk = ({ dispatch, getState }) => next => action => {
  console.info("thunk");
  if (typeof action === "function") {
    return action(dispatch, getState, extraArgument);
  }
  return next(action);
};

const compose = (...funcs) => {
  if (funcs.length === 0) {
    return arg => arg;
  }

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

  return funcs.reduce((pre, cur) => {
    // console.info("xx");
    // console.info(pre.toString());
    // console.info(cur.toString());
    const func = (...args) => pre(cur(...args));
    console.info(func.toString());
    return func;
  });
};

// 组合后的代码
/* (...args) => thunk()(logger()(...args));

thunk()(logger()(dispatch)); */

/* logger()(dispatch) 返回的是最里层的函数,
该函数作为参数传递给了thunk的next参数,thunk执行完了返回的也是
最后一个函数,这个函数最后被export出去,
在UI里dispatch的时候其实直接执行的是thunk返回的最后的函数,
因为闭包的原因,next会保存在该函数中,调用next的话,其实就是在调用
logger最后的函数,然后logger里再调用 next(action) 这个时候
next就是redux没有经过自定义包装的原生的dispatch,
这就是执行本文代码的时候,为什么会打印顺序会如下的原因
thunk
logger will dispatch
final args
logger state after dispatch */

const applyMiddleware = (...middlewares) => {
  let dispatch = () => {
    /*    throw new Error(
      `Dispatching while constructing your middleware is not allowed. ` +
        `Other middleware would not be applied to this dispatch.`
    ); */

    console.info("final args");
  };

  const middlewareAPI = {
    getState: () => ({ state: {} }),
    dispatch: (...args) => dispatch(...args)
  };

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

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

  return dispatch;
};

const dispatch = applyMiddleware(thunk, logger);

// console.dir(dispatch);

dispatch({ type: "test", payload: { name: "geek" } });

logger()(dispatch) 返回的是最里层的函数,该函数作为参数传递给了thunk的next参数,thunk执行完了返回的也是最后一个函数,这个函数最后被export出去,在UI里dispatch的时候其实直接执行的是thunk返回的最后的函数,因为闭包的原因,next会保存在该函数中,调用next的话,其实就是在调用logger最后的函数,然logger里再调用 next(action) 这个时候next就是redux没有经过自定义包装的原生的dispatch,这就是执行本文代码的时候,为什么会打印顺序会如下的原因
thunk
logger will dispatch
final args
logger state after dispatch
也就是说我们在UI里dispatch的时候执行的顺序是:thunk的最后的函数=>logger最后的函数=>redux的dispatch

但是问题来了,redux的dispatch其实是经过包装的

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)//后面dispatch其实也是经过compose的函数
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

dispatch: (...args) => dispatch(...args) 后面dispatch其实也是经过compose的函数,再看看我们如何调用thunk的

function makeASandwichWithSecretSauce(dispatch) {
  return fetchSecretSauce().then(
    sauce => dispatch(makeASandwich(forPerson, sauce)),
    error => dispatch(apologize('The Sandwich Shop', forPerson, error))
  )
}

本来认为我们在thunk里调用的dispatch是redux自身的dispatch,其实不是,从源码可以看出,只要使用了中间件,经过applyMiddleware的处理那么redux的dispatch就经过了处理,所有传出去的dispatch都会经过中间件,那么在thunk dispatch数据的时候,理所当然再去走所有的中间件,直到走到redux createStore中的dispatch函数把payload数据装载到reducer函数currentState = currentReducer(currentState, action);这样一条异步数据请求到改变redux的state就走完了

image.png
image.png

上面的redux-ui的node_modules下也有redux 断点打在了这里的createStore的dispatch函数,一直没执行,所以还是需要把断点打在最外面的redux上就行了,中间F11还会进入VM43360貌似是浏览器内置的函数,不知道为什么,路漫漫其修远兮

我项目里applyMiddleware(thunk, routerMiddleware(history))那么在所有的dispatch的时候都会经过这两个中间件,如果任意中间件中做了判断没有执行下个next那么该最后就不会执行redux createStore中的dispatch,从而不能调用reducer,那么redux state就不会改变
比如thunk

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
     // 此处被拦截 next不会执行,createStore中的dispatch就不会被执行,
     // redux state就不会改变,也就是dispatch被拦截了
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

如果dispatch一个普通的数据,那么流程就是thunk=>routerMiddleware=>redux createStore dispatch

  1. 从函数组合看middleware

柯里化重要的作用是用于函数组合

一个合格的中级前端工程师必须要掌握的 28 个 JavaScript 技巧提到

函数式编程另一个重要的函数 compose,能够将函数进行组合,而组合的函数只接受一个参数,所以如果有接受多个函数的需求并且需要用到 compose 进行函数组合,就需要使用柯里化对准备组合的函数进行部分求值,让它始终只接受一个参数

简化applyMiddleware.js


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;

const logger = store => next => action => {
  console.log("dispatching", action);
  let result = next(action);
  console.log("next state", store.getState());
  return result;
};

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

function applyMiddleware(...middlewares) {
  const store = {
    dispatch: () => {
      console.info("dispatch");
    }
  };
  const middlewareAPI = {
    getState: store.getState,
    dispatch: (...args) => dispatch(...args)
  };
  const chain = middlewares.map(middleware => middleware(middlewareAPI));
  console.dirxml(chain);
  dispatch = compose(...chain)(store.dispatch);

  return {
    ...store,
    dispatch
  };
}

applyMiddleware(thunk, logger);

chain

从上图可以看出chain就是下面的这种结构

const logger = next => action => {
  console.log("dispatching", action);
  let result = next(action);
  console.log("next state", store.getState());
  return result;
};

const thunk = next => action => {
  if (typeof action === "function") {
    return action(dispatch, getState, extraArgument);//thunk 执行到这里在redux里的操作就终止了,
// 也就是不会去改变redux里的state,其实是在action函数的内部使用dispatch去修改state,
// 为什么要return  action(dispatch, getState, extraArgument) 
// 这样在调用diapatch thunk的时候可以直接拿到异步操作的返回值
  }

  return next(action);
};

然后compose,得到

logger(thunk(dispatch));

被组合的函数logger 和thunk是参数为next的单参数函数,同时返回一个函数作为下个middleware的参数,结合函数组合被组合的函数的参数肯定只有一个,被组合的函数的返回值作为下个函数的参数 那么应该跟传入的参数的数据类型是一样的,比如参数是number那么返回值也是number,参数是function,返回值也是function。

再看看redux的思想,改变state只有一种方式,那就是dispatch,但是redux默认不支持异步操作,这个时候就需要middleware来辅助,dispatch进行拦截。next就是上一个action=>{}, next和action=>{},就是redux原生定义的dispatch的结构,再看最后得到的dispatch

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