成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Redux中間件對(duì)閉包的一個(gè)巧妙使用

moven_j / 2801人閱讀

摘要:最近在看的源碼,發(fā)現(xiàn)在使用中間件的源碼中,有一個(gè)對(duì)閉包非常巧妙的使用,解決了雞生蛋,蛋生雞的問(wèn)題,特分享給大家。中間件的函數(shù)簽名形式如下函數(shù)體中的函數(shù)用于根據(jù)中間件生成經(jīng)過(guò)的中間件鏈。

最近在看Redux的源碼,發(fā)現(xiàn)Redux在使用中間件applyMiddleware.js的源碼中,有一個(gè)對(duì)閉包非常巧妙的使用,解決了“雞生蛋,蛋生雞”的問(wèn)題,特分享給大家。

Redux中間件的函數(shù)簽名形式如下:

({dispatch, getState}) => next => action => {
   // 函數(shù)體
}

applyMiddleware.js中的函數(shù)applyMiddleware(...middlewares)用于根據(jù)中間件生成action經(jīng)過(guò)的中間件鏈。先來(lái)看一個(gè)錯(cuò)誤版本的實(shí)現(xiàn):

/*
 * @param {...Function} middlewares The middleware chain to be applied.
 * @returns {Function} A store enhancer applying the middleware.
 */
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, initialState, enhancer) => {
    var store = createStore(reducer, initialState, enhancer)
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: store.dispatch
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    var dispatch = compose(...chain)(store.dispatch)    //compose(f, g, h) 等價(jià)于函數(shù)
                                                    //(...args)=>f(g(h(args)))

    return {
      ...store,
      dispatch
    }
  }

核心邏輯是chain = middlewares.map(middleware => middleware(middlewareAPI))dispatch = compose(...chain)(store.dispatch)這兩行。第1句代碼是根據(jù)中間件生成一個(gè)數(shù)組chain,chain的元素是簽名為next => action => {...}形式的函數(shù),每個(gè)元素就是最終中間件鏈上的一環(huán)。第2句代碼利用compose函數(shù),將chain中的函數(shù)元素組成一個(gè)“洋蔥式”的大函數(shù),chain的每個(gè)函數(shù)元素相當(dāng)于一層洋蔥表皮。Redux發(fā)送的每一個(gè)action都會(huì)由外到內(nèi)依次經(jīng)過(guò)每一層函數(shù)的處理。假設(shè)有3層函數(shù),從外到內(nèi)依次是a,b,c,函數(shù)的實(shí)際調(diào)用過(guò)程是,a接收到action,在a函數(shù)體內(nèi)會(huì)調(diào)用b(a的參數(shù)next,指向的就是b),并把a(bǔ)ction傳遞給b,然后b調(diào)用c(b的參數(shù)next指向的就是c),同時(shí)也把a(bǔ)ction傳遞給c,c的參數(shù)next指向的是原始的store.dispatch,因此是action dispatch的最后一環(huán)。這樣分析下來(lái),程序是沒(méi)有問(wèn)題的,但當(dāng)我們的中間件需要直接使用dispatch函數(shù)時(shí),問(wèn)題就出來(lái)了。例如,常用于發(fā)送異步action的中間件redux-thunk,就需要在異步action中使用dispatch:

export function fetchPosts(subreddit) {  
  return function (dispatch) {
    dispatch(requestPosts(subreddit))
    return fetch(`https://www.reddit.com/r/${subreddit}.json`)
      .then(
        response => response.json(),
        error => console.log("An error occured.", error)
      )
      .then(json =>
        dispatch(receivePosts(subreddit, json))
      )
  }
}

fetchPosts使用的dispatch,是redux-thunk傳遞過(guò)來(lái)的,指向的是middlewareAPI對(duì)象中的dispatch,實(shí)際等于store.dispatch。當(dāng)執(zhí)行dispatch(requestPosts(subreddit))時(shí),這個(gè)action直接就到了最后一環(huán)節(jié)的處理,跳過(guò)了redux-thunk中間件之后的其他中間件的處理,顯然是不合適的。我們希望的方式是,這個(gè)action依然會(huì)從最外層的中間件開(kāi)始,由外到內(nèi)經(jīng)過(guò)每一層中間件的處理。所以,這里使用的dispatch函數(shù)不能等于store.dispatch,應(yīng)該等于compose(...chain)(store.dispatch),只有這樣,發(fā)送的action才能經(jīng)過(guò)每一層中間件的處理?,F(xiàn)在問(wèn)題出來(lái)了,chain = middlewares.map(middleware => middleware(middlewareAPI))需要使用dispatch = compose(...chain)(store.dispatch)返回的dispatch函數(shù),而dispatch = compose(...chain)(store.dispatch)的執(zhí)行又依賴于chain = middlewares.map(middleware => middleware(middlewareAPI))的執(zhí)行結(jié)果,我們進(jìn)入死循環(huán)了。

問(wèn)題的解決方案就是閉包。當(dāng)我們定義middlewareAPI的dispatch時(shí),不直接把它指向store.dispatch,而是定義一個(gè)新的函數(shù),在函數(shù)中引用外部的一個(gè)局部變量dispatch,這樣就形成了一個(gè)閉包,外部dispatch變量的變化會(huì)同步反映到內(nèi)部函數(shù)中。如下所示:

export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, initialState, enhancer) => {
    var store = createStore(reducer, initialState, enhancer)
    var dispatch = store.dispatch;   // 需要有初始值,保證中間件在初始化過(guò)程中也可以正常使用dispatch
    var chain = []

    var middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)    // 通過(guò)閉包引用外部的dispatch變量
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)    //compose(f, g, h) 等價(jià)于函數(shù)
                                                    //(...args)=>f(g(h(args)))

    return {
      ...store,
      dispatch
    }
  }

這樣,“雞生蛋,蛋生雞”的問(wèn)題就解決了。如果這個(gè)例子對(duì)你來(lái)說(shuō)太復(fù)雜,可以用下面這個(gè)簡(jiǎn)化的例子幫助你理解:

const middleware = ({dispatch}) => (next) => (number) => {
  console.log("in middleware");
  if(number !== 0){
    return dispatch(--number);
  }
  
  return next(number);
}

function test() {
  var dispatch = (number) => { 
    console.log("original dispatch");
    return number;
  };
  var middlewareAPI = {
    dispatch
  }
  
  dispatch = middleware(middlewareAPI)(dispatch);
  
  return {
    dispatch
  }
}

var {dispatch} = test();
dispatch(3);

//輸出:
"in middleware"
"original dispatch"

const middleware = ({dispatch}) => (next) => (number) => {
  console.log("in middleware");
  if(number !== 0){
    return dispatch(--number);
  }
  
  return next(number);
}

function test() {
  var dispatch = (number) => { 
    console.log("original dispatch");
    return number;
  };
  var middlewareAPI = {
    dispatch: (number) => {dispatch(number);}
  }
  
  dispatch = middleware(middlewareAPI)(dispatch);
  
  return {
    dispatch
  }
}

var {dispatch} = test();
dispatch(3);

//輸出 
"in middleware"
"in middleware"
"in middleware"
"in middleware"
"original dispatch"

第二種方式,middleware中dispatch的number會(huì)再次經(jīng)歷中間件的處理,當(dāng)number=3,2,1,0時(shí),都會(huì)進(jìn)入一次middleware函數(shù),當(dāng)number=0時(shí),next(0)調(diào)用的是test中定義的初始dispatch函數(shù),因此不再經(jīng)過(guò)middleware的處理。

歡迎關(guān)注我的公眾號(hào):老干部的大前端,領(lǐng)取21本大前端精選書(shū)籍!

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/88551.html

相關(guān)文章

  • 2017-09-12 前端日?qǐng)?bào)

    摘要:前端日?qǐng)?bào)精選第一問(wèn)連接的問(wèn)題該怎么答路由輕量級(jí)函數(shù)式編程第章閉包對(duì)象開(kāi)始使用和近階段學(xué)習(xí)總結(jié)中文基礎(chǔ)圖表之一掘金中的模式匹配眾成翻譯字符串的擴(kuò)展設(shè)計(jì)模式系列二之建造者模式附案例源碼掘金通告教你寫(xiě)完美簡(jiǎn)歷應(yīng)聘名企外企知 2017-09-12 前端日?qǐng)?bào) 精選 第一問(wèn):TCP連接的問(wèn)題該怎么答Vue-Router(vue路由)JavaScript輕量級(jí)函數(shù)式編程-第7章: 閉包vs對(duì)象開(kāi)始使...

    EasonTyler 評(píng)論0 收藏0
  • redux源碼總結(jié)

    摘要:源碼個(gè)人覺(jué)得后的代碼就是,后返回的任然是一個(gè)函數(shù),可以接受參數(shù),在后面介紹的代碼中有所涉及。源碼中的獲取對(duì)象供后面使用,里的對(duì)應(yīng)著對(duì)象里的,方法可以獲取,也就是對(duì)象樹(shù)的總數(shù)據(jù)。 redux介紹 redux給我們暴露了這幾個(gè)方法 { createStore, combineReducers, bindActionCreators, applyMiddleware, c...

    worldligang 評(píng)論0 收藏0
  • redux源碼總結(jié)

    摘要:源碼個(gè)人覺(jué)得后的代碼就是,后返回的任然是一個(gè)函數(shù),可以接受參數(shù),在后面介紹的代碼中有所涉及。源碼中的獲取對(duì)象供后面使用,里的對(duì)應(yīng)著對(duì)象里的,方法可以獲取,也就是對(duì)象樹(shù)的總數(shù)據(jù)。 redux介紹 redux給我們暴露了這幾個(gè)方法 { createStore, combineReducers, bindActionCreators, applyMiddleware, c...

    高璐 評(píng)論0 收藏0
  • 高級(jí)前端面試題大匯總(只有試題,沒(méi)有答案)

    摘要:面試題來(lái)源于網(wǎng)絡(luò),看一下高級(jí)前端的面試題,可以知道自己和高級(jí)前端的差距。 面試題來(lái)源于網(wǎng)絡(luò),看一下高級(jí)前端的面試題,可以知道自己和高級(jí)前端的差距。有些面試題會(huì)重復(fù)。 使用過(guò)的koa2中間件 koa-body原理 介紹自己寫(xiě)過(guò)的中間件 有沒(méi)有涉及到Cluster 介紹pm2 master掛了的話pm2怎么處理 如何和MySQL進(jìn)行通信 React聲明周期及自己的理解 如何...

    kviccn 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

moven_j

|高級(jí)講師

TA的文章

閱讀更多
最新活動(dòng)
閱讀需要支付1元查看
<