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