摘要:最近在看的源碼,發(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
摘要:前端日?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)始使...
摘要:源碼個(gè)人覺(jué)得后的代碼就是,后返回的任然是一個(gè)函數(shù),可以接受參數(shù),在后面介紹的代碼中有所涉及。源碼中的獲取對(duì)象供后面使用,里的對(duì)應(yīng)著對(duì)象里的,方法可以獲取,也就是對(duì)象樹(shù)的總數(shù)據(jù)。 redux介紹 redux給我們暴露了這幾個(gè)方法 { createStore, combineReducers, bindActionCreators, applyMiddleware, c...
摘要:源碼個(gè)人覺(jué)得后的代碼就是,后返回的任然是一個(gè)函數(shù),可以接受參數(shù),在后面介紹的代碼中有所涉及。源碼中的獲取對(duì)象供后面使用,里的對(duì)應(yīng)著對(duì)象里的,方法可以獲取,也就是對(duì)象樹(shù)的總數(shù)據(jù)。 redux介紹 redux給我們暴露了這幾個(gè)方法 { createStore, combineReducers, bindActionCreators, applyMiddleware, c...
摘要:面試題來(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聲明周期及自己的理解 如何...
閱讀 1273·2021-09-23 11:51
閱讀 1392·2021-09-04 16:45
閱讀 634·2019-08-30 15:54
閱讀 2088·2019-08-30 15:52
閱讀 1606·2019-08-30 11:17
閱讀 3108·2019-08-29 13:59
閱讀 2023·2019-08-28 18:09
閱讀 390·2019-08-26 12:15