摘要:函數(shù)的柯里化的基本使用方法和函數(shù)綁定是一樣的使用一個閉包返回一個函數(shù)。先來一段我自己實現(xiàn)的函數(shù)高程里面這么評價它們兩個的方法也實現(xiàn)了函數(shù)的柯里化。使用還是要根據(jù)是否需要對象響應來決定。
奇怪,怎么把函數(shù)的柯里化和Redux中間件這兩個八竿子打不著的東西聯(lián)系到了一起,如果你和我有同樣疑問的話,說明你對Redux中間件的原理根本就不了解,我們先來講下什么是函數(shù)的柯里化?再來講下Redux的中間件及applyMiddleware源碼
查看demo
查看源碼,歡迎star
高階函數(shù)提及函數(shù)的柯里化,就必須先說一下高階函數(shù)(high-order function),高階函數(shù)是滿足下面兩個條件其中一個的函數(shù):
函數(shù)可以作為參數(shù)
函數(shù)可以作為返回值
看到這個,大家應該秒懂了吧,像我們平時使用的setTimeout,map,filter,reduce等都屬于高階函數(shù),當然還有我們今天要說的函數(shù)的柯里化,也是高階函數(shù)的一種應用
函數(shù)的柯里化什么是函數(shù)的柯里化?看過JS高程一書的人應該知道有一章是專門講JS高級技巧的,其中對于函數(shù)的柯里化是這樣描述的:
它用于創(chuàng)建已經(jīng)設置好了一個或多個參數(shù)的函數(shù)。函數(shù)的柯里化的基本使用方法和函數(shù)綁定是一樣的:使用一個閉包返回一個函數(shù)。兩者的區(qū)別在于,當函數(shù)被調(diào)用時,返回的函數(shù)還需要設置一些傳入的參數(shù)
聽得有點懵逼是吧,來看一個例子
const add = (num1, num2) => { return num1 + num2 } const sum = add(1, 2)
add是一個返回兩個參數(shù)和的函數(shù),而如果要對add進行柯里化改造,就像下面這樣
const curryAdd = (num1) => { return (num2) => { return num1 + num2 } } const sum = curryAdd(1)(2)
更通用的寫法如下:
const curry = (fn, ...initArgs) => { let finalArgs = [...initArgs] return (...otherArgs) => { finalArgs = [...finalArgs, ...otherArgs] if (otherArgs.length === 0) { return fn.apply(this, finalArgs) } else { return curry.call(this, fn, ...finalArgs) } } }
我們在對我們的add進行改造來讓它可以接收任意個參數(shù)
const add = (...args) => args.reduce((a, b) => a + b)
再用我們上面寫的curry對add進行柯里化改造
const curryAdd = curry(add) curryAdd(1) curryAdd(2, 5) curryAdd(3, 10) curryAdd(4) const sum = curryAdd() // 25
注意我們最后必須調(diào)用curryAdd()才能返回操作結(jié)果,你也可以對curry進行改造,當傳入的參數(shù)的個數(shù)達到fn指定的參數(shù)個數(shù)就返回操作結(jié)果
總之函數(shù)的柯里化就是將多參數(shù)函數(shù)轉(zhuǎn)換成單參數(shù)函數(shù),這里的單參數(shù)并不僅僅指的是一個參數(shù),我的理解是參數(shù)切分
PS:敏感的同學應該看出來了,這個和ES5的bind函數(shù)的實現(xiàn)很像。先來一段我自己實現(xiàn)的bind函數(shù)
Function.prototype.bind = function(context, ...initArgs) { const fn = this let args = [...initArgs] return function(...otherArgs) { args = [...args, ...otherArgs] return fn.call(context, ...args) } } var obj = { name: "monkeyliu", getName: function() { console.log(this.name) } } var getName = obj.getName getName.bind(obj)() // monkeyliu
高程里面這么評價它們兩個:
ES5的bind方法也實現(xiàn)了函數(shù)的柯里化。使用bind還是curry要根據(jù)是否需要object對象響應來決定。它們都能用于創(chuàng)建復雜的算法和功能,當然兩者都不應濫用,因為每個函數(shù)都會帶來額外的開銷Redux中間件
什么是Redux中間件?我的理解是在dispatch(action)前后允許用戶添加屬于自己的代碼,當然這種理解可能并不是特別準確,但是對于剛接觸redux中間件的同學,這是理解它最好的一種方式
我會通過一個記錄日志和打印執(zhí)行時間的例子來幫助各位從分析問題到通過構(gòu)建 middleware 解決問題的思維過程
當我們dispatch一個action時,我們想記錄當前的action值,和記錄變化之后的state值該怎么做?
手動記錄最笨的辦法就是在dispatch之前,打印當前的action,在dispatch之后打印變化之后的state,你的代碼可能是這樣
const action = { type: "increase" } console.log("dispatching:", action) store.dispatch(action) console.log("next state:", store.getState())
這是一般的人都會想到的辦法,簡單,但是通用性較差,如果我們在多處都要記錄日志,上面的代碼會被寫多次
封裝Dispatch要想復用我們的代碼,我們會嘗試封裝下將上面那段代碼封裝成一個函數(shù)
const dispatchAndLog = action => { console.log("dispatching:", action) store.dispatch(action) console.log("next state:", store.getState()) }
但是這樣的話只是減少了我們的代碼量,在需要用到它的地方我們還是得每次引入這個方法,治標不治本
改造原生的dispatch直接覆蓋store.dispatch,這樣我們就不用每次引入dispatchAndLog,這種辦法網(wǎng)上人稱作monkeypatch(猴戲打補),你的代碼可能是這樣
const next = store.dispatch store.dispatch = action => { console.log("dispatching:", action) next(action) console.log("next state:", store.getState()) }
這樣已經(jīng)能做到一次改動,多處使用,已經(jīng)能達到我們想要的目的了,但是,it"s not over yet(還沒結(jié)束)
記錄執(zhí)行時間當我們除了要記錄日志外,還需要記錄dispatch前后的執(zhí)行時間,我們需要新建另外一個中間件,然后依次去執(zhí)行這兩個,你的代碼可能是這樣
const logger = store => { const next = store.dispatch store.dispatch = action => { console.log("dispatching:", action) next(action) console.log("next state:", store.getState()) } } const date = store => { const next = store.dispatch store.dispatch = action => { const date1 = Date.now() console.log("date1:", date1) next(action) const date2 = Date.now() console.log("date2:", date2) } } logger(store) date(store)
但是這樣的話,打印結(jié)果如下:
date1: dispatching: next state: date2:
中間件輸出的結(jié)果和中間件執(zhí)行的順序相反
利用高階函數(shù)如果我們在logger和date中不去覆蓋store.dispatch,而是利用高階函數(shù)返回一個新的函數(shù),結(jié)果又是怎樣呢?
const logger = store => { const next = store.dispatch return action => { console.log("dispatching:", action) next(action) console.log("next state:", store.getState()) } } const date = store => { const next = store.dispatch return action => { const date1 = Date.now() console.log("date1:", date1) next(action) const date2 = Date.now() console.log("date2:", date2) } }
然后我們需要創(chuàng)建一個函數(shù)來接收logger和date,在這個函數(shù)體里面我們循環(huán)遍歷它們,將他們賦值給store.dispatch,這個函數(shù)就是applyMiddleware的雛形
const applyMiddlewareByMonkeypatching = (store, middlewares) => { middlewares.reverse() middlewares.map(middleware => { store.dispatch = middleware(store) }) }
然后我們可以這樣應用我們的中間件
applyMiddlewareByMonkeypatching(store, [logger, date])
但是這樣仍然屬于猴戲打補,只不過我們將它的實現(xiàn)細節(jié),隱藏在applyMiddlewareByMonkeypatching內(nèi)部
結(jié)合函數(shù)柯里化中間件的一個重要特性就是后一個中間件能夠使用前一個中間件包裝過的store.dispatch,我們可以通過函數(shù)的柯里化實現(xiàn),我們將之前的logger和date改造了下
const logger = store => next => action => { console.log("dispatching:", action) next(action) console.log("next state:", store.getState()) } const date = store => next => action => { const date1 = Date.now() console.log("date1:", date1) next(action) const date2 = Date.now() console.log("date2:", date2) }
redux的中間件都是上面這種寫法,next為上一個中間件返回的函數(shù),并返回一個新的函數(shù)作為下一個中間件next的輸入值
為此我們的applyMiddlewareByMonkeypatching也需要被改造下,我們將其命名為applyMiddleware
const applyMiddleware = (store, middlewares) => { middlewares.reverse() let dispatch = store.dispatch middlewares.map(middleware => { dispatch = middleware(store)(dispatch) }) return { ...store, dispatch } }
我們可以這樣使用它
let store = createStore(reducer) store = applyMiddleware(store, [logger, date])
這個applyMiddleware就是我們自己動手實現(xiàn)的,當然它跟redux提供的applyMiddleware還是有一定的區(qū)別,我們來分析下原生的applyMiddleware的源碼就可以知道他們之間的差異了
applyMiddleware源碼直接上applyMiddleware的源碼
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) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
原生的applyMiddleware是放在createStore的第二個參數(shù),我們也貼下createStore的相關核心代碼,然后結(jié)合二者一起分析
export default function createStore(reducer, preloadedState, enhancer) { if (typeof preloadedState === "function" && typeof enhancer === "undefined") { enhancer = preloadedState preloadedState = undefined } if (typeof enhancer !== "undefined") { if (typeof enhancer !== "function") { throw new Error("Expected the enhancer to be a function.") } return enhancer(createStore)(reducer, preloadedState) } .... }
當傳入了applyMiddleware,此時最后執(zhí)行enhancer(createStore)(reducer, preloadedState)并返回一個store對象,enhancer就是我們傳入的applyMiddleware,我們先執(zhí)行它并返回一個函數(shù),該函數(shù)帶有一個createStore參數(shù),接著我們繼續(xù)執(zhí)行enhancer(createStore)又返回一個函數(shù),最后我們執(zhí)行enhancer(createStore)(reducer, preloadedState),我們來分析這個函數(shù)體內(nèi)做了些什么事?
const store = createStore(...args)
首先利用reducer和preloadedState來創(chuàng)建一個store對象
let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) }
這句代碼的意思就是在構(gòu)建中間件的過程不可以調(diào)用dispath函數(shù),否則會拋出異常
const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) }
定義middlewareAPI對象包含兩個屬性getState和dispatch,該對象用來作為中間件的輸入?yún)?shù)store
const chain = middlewares.map(middleware => middleware(middlewareAPI))
chain是一個數(shù)組,數(shù)組的每一項是一個函數(shù),該函數(shù)的入?yún)⑹莕ext,返回另外一個函數(shù)。數(shù)組的每一項可能是這樣
const a = next => { return action => { console.log("dispatching:", action) next(action) } }
最后幾行代碼
dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch }
其中compose的實現(xiàn)代碼如下
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))) }
compose是一個歸并方法,當不傳入funcs,將返回一個arg => arg函數(shù),當funcs長度為1,將返回funcs[0],當funcs長度大于1,將作一個歸并操作,我們舉個例子
const func1 = (a) => { return a + 3 } const func2 = (a) => { return a + 2 } const func3 = (a) => { return a + 1 } const chain = [func1, func2, func3] const func4 = compose(...chain)
func4是這樣的一個函數(shù)
func4 = (args) => func1(func2(func3(args)))
所以上述的dispatch = compose(...chain)(store.dispatch)就是這么一個函數(shù)
const chain = [logger, date] dispatch = compose(...chain)(store.dispatch) // 等價于 dispatch = action => logger(date(store.dispatch))
最后在把store對象傳遞出去,用我們的dispatch覆蓋store中的dispatch
return { ...store, dispatch }
到此整個applyMiddleware的源碼分析完成,發(fā)現(xiàn)也沒有想象中的那么神秘,永遠要保持一顆求知欲
和手寫的applyMiddleware的區(qū)別差點忘記了這個,講完了applyMiddleware的源碼,在來說說和我上述自己手寫的applyMiddleware的區(qū)別,區(qū)別有三:
原生的只提供了getState和dispatch,而我手寫的提供了store中所有的屬性和方法
原生的middleware只能應用一次,因為它是作用在createStore上;而我自己手寫的是作用在store上,它可以被多次調(diào)用
原生的可以在middleware中調(diào)用store.dispatch方法不產(chǎn)生任何副作用,而我們手寫的會覆蓋store.dispatch方法,原生的這種實現(xiàn)方式對于異步的middle非常有用
最后查看demo
查看源碼,歡迎star
你們的打賞是我寫作的動力
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/96645.html
摘要:執(zhí)行完后,獲得數(shù)組,,它保存的對象是圖中綠色箭頭指向的匿名函數(shù),因為閉包,每個匿名函數(shù)都可以訪問相同的,即。是函數(shù)式編程中的組合,將中的所有匿名函數(shù),,組裝成一個新的函數(shù),即新的,當新執(zhí)行時,,從左到右依次執(zhí)行所以順序很重要。 前言 It provides a third-party extension point between dispatching anaction, and t...
摘要:最后看一下這時候執(zhí)行返回,如下調(diào)用執(zhí)行循序調(diào)用第層中間件返回即調(diào)用第層中間件返回即調(diào)用根返回即調(diào)用一個例子讀懂上文提到是個柯里化函數(shù),可以看成是將所有函數(shù)合并成一個函數(shù)并返回的函數(shù)。 由于一直用業(yè)界封裝好的如redux-logger、redux-thunk此類的中間件,并沒有深入去了解過redux中間件的實現(xiàn)方式。正好前些時間有個需求需要對action執(zhí)行時做一些封裝,于是借此了解了下...
摘要:用法源碼由在年創(chuàng)建的科技術(shù)語。我們除去源碼校驗函數(shù)部分,從最終返回的大的來看。這個返回值無法被識別。洋蔥模型我們來看源碼源碼每個都以作為參數(shù)進行注入,返回一個新的鏈。改變原始組數(shù),是一種副作用。 @(Redux)[|用法|源碼] Redux 由Dan Abramov在2015年創(chuàng)建的科技術(shù)語。是受2014年Facebook的Flux架構(gòu)以及函數(shù)式編程語言Elm啟發(fā)。很快,Redux因其...
摘要:接下來的函數(shù)就有點難度了,讓我們一行一行來看。上面實際的含義就是將數(shù)組每一個執(zhí)行的返回值保存的數(shù)組中。需要注意的是,方法返回值并不是數(shù)組,而是形如初始值的經(jīng)過疊加處理后的操作。從而實現(xiàn)異步的。 這段時間都在學習Redux,感覺對我來說初學難度很大,中文官方文檔讀了好多遍才大概有點入門的感覺,小小地總結(jié)一下,首先可以看一下Redux的基本流程:showImg(https://segm...
閱讀 1354·2023-04-25 19:33
閱讀 1227·2021-10-21 09:39
閱讀 3703·2021-09-09 09:32
閱讀 2680·2019-08-30 10:58
閱讀 1705·2019-08-29 16:17
閱讀 916·2019-08-29 15:29
閱讀 2948·2019-08-26 11:55
閱讀 2704·2019-08-26 10:33