redux middleware 理解中

redux@4.0.0

const logger = ({ dispatch, getState }) => next => action => {
  console.log("logger will dispatch");
  // 調(diào)用 middleware 鏈中下一個(gè) middleware 的 dispatch污朽。
  let returnValue = next(action);
  console.log("logger state after dispatch");
  return returnValue;
};

/*  最后面的函數(shù)是自己的dispatch
 next是別人的dispatch
 next就是包裝后的dispatch就是個(gè)函數(shù),然后返回一個(gè)新的dispatch函數(shù)跟高階組件一
 樣耽装,一個(gè)是函數(shù)接收一個(gè)組件返回包裝后的新組件愤炸,這邊中間件就是接收個(gè)函數(shù)返回個(gè)新函數(shù) */

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) 返回的是最里層的函數(shù),
該函數(shù)作為參數(shù)傳遞給了thunk的next參數(shù)掉奄,thunk執(zhí)行完了返回的也是
最后一個(gè)函數(shù)规个,這個(gè)函數(shù)最后被export出去,
在UI里dispatch的時(shí)候其實(shí)直接執(zhí)行的是thunk返回的最后的函數(shù)姓建,
因?yàn)殚]包的原因诞仓,next會(huì)保存在該函數(shù)中,調(diào)用next的話引瀑,其實(shí)就是在調(diào)用
logger最后的函數(shù)狂芋,然后logger里再調(diào)用 next(action) 這個(gè)時(shí)候
next就是redux沒有經(jīng)過自定義包裝的原生的dispatch榨馁,
這就是執(zhí)行本文代碼的時(shí)候憨栽,為什么會(huì)打印順序會(huì)如下的原因
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) 返回的是最里層的函數(shù),該函數(shù)作為參數(shù)傳遞給了thunk的next參數(shù)翼虫,thunk執(zhí)行完了返回的也是最后一個(gè)函數(shù)屑柔,這個(gè)函數(shù)最后被export出去,在UI里dispatch的時(shí)候其實(shí)直接執(zhí)行的是thunk返回的最后的函數(shù)珍剑,因?yàn)殚]包的原因掸宛,next會(huì)保存在該函數(shù)中,調(diào)用next的話招拙,其實(shí)就是在調(diào)用logger最后的函數(shù)唧瘾,然logger里再調(diào)用 next(action) 這個(gè)時(shí)候next就是redux沒有經(jīng)過自定義包裝的原生的dispatch措译,這就是執(zhí)行本文代碼的時(shí)候,為什么會(huì)打印順序會(huì)如下的原因
thunk
logger will dispatch
final args
logger state after dispatch
也就是說我們?cè)赨I里dispatch的時(shí)候執(zhí)行的順序是:thunk的最后的函數(shù)=>logger最后的函數(shù)=>redux的dispatch

但是問題來了饰序,redux的dispatch其實(shí)是經(jīng)過包裝的

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其實(shí)也是經(jīng)過compose的函數(shù)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

dispatch: (...args) => dispatch(...args) 后面dispatch其實(shí)也是經(jīng)過compose的函數(shù)领虹,再看看我們?nèi)绾握{(diào)用thunk的

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

本來認(rèn)為我們?cè)趖hunk里調(diào)用的dispatch是redux自身的dispatch,其實(shí)不是求豫,從源碼可以看出塌衰,只要使用了中間件,經(jīng)過applyMiddleware的處理那么redux的dispatch就經(jīng)過了處理蝠嘉,所有傳出去的dispatch都會(huì)經(jīng)過中間件,那么在thunk dispatch數(shù)據(jù)的時(shí)候最疆,理所當(dāng)然再去走所有的中間件,直到走到redux createStore中的dispatch函數(shù)把payload數(shù)據(jù)裝載到reducer函數(shù)currentState = currentReducer(currentState, action);這樣一條異步數(shù)據(jù)請(qǐng)求到改變r(jià)edux的state就走完了

image.png
image.png

上面的redux-ui的node_modules下也有redux 斷點(diǎn)打在了這里的createStore的dispatch函數(shù)蚤告,一直沒執(zhí)行努酸,所以還是需要把斷點(diǎn)打在最外面的redux上就行了,中間F11還會(huì)進(jìn)入VM43360貌似是瀏覽器內(nèi)置的函數(shù)杜恰,不知道為什么蚊逢,路漫漫其修遠(yuǎn)兮

我項(xiàng)目里applyMiddleware(thunk, routerMiddleware(history))那么在所有的dispatch的時(shí)候都會(huì)經(jīng)過這兩個(gè)中間件,如果任意中間件中做了判斷沒有執(zhí)行下個(gè)next那么該最后就不會(huì)執(zhí)行redux createStore中的dispatch箫章,從而不能調(diào)用reducer,那么redux state就不會(huì)改變
比如thunk

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {
     // 此處被攔截 next不會(huì)執(zhí)行烙荷,createStore中的dispatch就不會(huì)被執(zhí)行,
     // redux state就不會(huì)改變檬寂,也就是dispatch被攔截了
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

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

export default thunk;

如果dispatch一個(gè)普通的數(shù)據(jù)终抽,那么流程就是thunk=>routerMiddleware=>redux createStore dispatch

  1. 從函數(shù)組合看middleware

柯里化重要的作用是用于函數(shù)組合

一個(gè)合格的中級(jí)前端工程師必須要掌握的 28 個(gè) JavaScript 技巧提到

函數(shù)式編程另一個(gè)重要的函數(shù) compose,能夠?qū)⒑瘮?shù)進(jìn)行組合桶至,而組合的函數(shù)只接受一個(gè)參數(shù)昼伴,所以如果有接受多個(gè)函數(shù)的需求并且需要用到 compose 進(jìn)行函數(shù)組合,就需要使用柯里化對(duì)準(zhǔn)備組合的函數(shù)進(jìn)行部分求值镣屹,讓它始終只接受一個(gè)參數(shù)

簡(jiǎn)化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就是下面的這種結(jié)構(gòu)

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 執(zhí)行到這里在redux里的操作就終止了圃郊,
// 也就是不會(huì)去改變r(jià)edux里的state,其實(shí)是在action函數(shù)的內(nèi)部使用dispatch去修改state女蜈,
// 為什么要return  action(dispatch, getState, extraArgument) 
// 這樣在調(diào)用diapatch thunk的時(shí)候可以直接拿到異步操作的返回值
  }

  return next(action);
};

然后compose持舆,得到

logger(thunk(dispatch));

被組合的函數(shù)logger 和thunk是參數(shù)為next的單參數(shù)函數(shù),同時(shí)返回一個(gè)函數(shù)作為下個(gè)middleware的參數(shù)伪窖,結(jié)合函數(shù)組合被組合的函數(shù)的參數(shù)肯定只有一個(gè)逸寓,被組合的函數(shù)的返回值作為下個(gè)函數(shù)的參數(shù) 那么應(yīng)該跟傳入的參數(shù)的數(shù)據(jù)類型是一樣的,比如參數(shù)是number那么返回值也是number覆山,參數(shù)是function竹伸,返回值也是function。

再看看redux的思想簇宽,改變state只有一種方式勋篓,那就是dispatch吧享,但是redux默認(rèn)不支持異步操作,這個(gè)時(shí)候就需要middleware來輔助譬嚣,dispatch進(jìn)行攔截耙蔑。next就是上一個(gè)action=>{}, next和action=>{},就是redux原生定義的dispatch的結(jié)構(gòu)孤荣,再看最后得到的dispatch

dispatch
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末甸陌,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子盐股,更是在濱河造成了極大的恐慌钱豁,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疯汁,死亡現(xiàn)場(chǎng)離奇詭異牲尺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)幌蚊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門谤碳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溢豆,你說我怎么就攤上這事蜒简。” “怎么了漩仙?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵搓茬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我队他,道長(zhǎng)卷仑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任麸折,我火速辦了婚禮锡凝,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘垢啼。我一直安慰自己窜锯,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布膊夹。 她就那樣靜靜地躺著衬浑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪放刨。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天尸饺,我揣著相機(jī)與錄音进统,去河邊找鬼助币。 笑死,一個(gè)胖子當(dāng)著我的面吹牛螟碎,可吹牛的內(nèi)容都是我干的眉菱。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼掉分,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼俭缓!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起酥郭,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤华坦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后不从,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惜姐,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年椿息,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了歹袁。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡寝优,死狀恐怖条舔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情乏矾,我是刑警寧澤逞刷,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布,位于F島的核電站妻熊,受9級(jí)特大地震影響夸浅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜扔役,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一帆喇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧亿胸,春花似錦坯钦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至序仙,卻和暖如春突颊,著一層夾襖步出監(jiān)牢的瞬間研乒,已是汗流浹背儿奶。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阵幸。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓箕肃,卻偏偏與公主長(zhǎng)得像恋博,于是被迫代替她去往敵國和親叨叙。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348