@(Redux)[|用法|源碼]javascript
Redux 由Dan Abramov在2015年建立的科技術語。是受2014年Facebook的Flux架構以及函數式編程語言Elm啓發。很快,Redux因其簡單易學體積小短期內成爲最熱門的前端架構。前端
@[三大原則]java
state
被儲存在一棵object tree
中,而且這個object tree
只存在於惟一一個store
中。全部數據會經過store.getState()
方法調用獲取.State
只讀原則,數據變動會經過store,dispatch(action)
方法.Reducer
只是一些純函數1,它接收先前的state
和action
,並返回新的state
.[TOC]git
//curry example const A = (a) => { return (b) => { return a + b } }
通俗的來說,能夠用一句話歸納柯里化函數:返回函數的函數.
優勢: 避免了給一個函數傳入大量的參數,將參數的代入分離開,更有利於調試。下降耦合度和代碼冗餘,便於複用.github
舉個例子編程
let init = (...args) => args.reduce((ele1, ele2) => ele1 + ele2, 0) let step2 = (val) => val + 2 let step3 = (val) => val + 3 let step4 = (val) => val + 4 let steps = [step4, step3, step2, init] let composeFunc = compose(...steps) console.log(composeFunc(1, 2, 3)) // 1+2+3+2+3+4 = 15
接下來看下FP思想的compose的源碼redux
const compose = function (...args) { let length = args.length let count = length - 1 let result let this_ = this // 遞歸 return function f1(...arg1) { result = args[count].apply(this, arg1) if (count <= 0) { count = length - 1 return result } count-- return f1.call(null, result) } }
通俗的講: 從右到左執行函數,最右函數以arguments爲參數,其他函數以上個函數結果爲入參數執行。api
優勢: 經過這樣函數之間的組合,能夠大大增長可讀性,效果遠大於嵌套一大堆的函數調用,而且咱們能夠隨意更改函數的調用順序數組
隨着整個項目愈來愈大,state
狀態樹也會愈來愈龐大,state的層級也會愈來愈深,因爲redux
只維護惟一的state
,當某個action.type
所對應的須要修改state.a.b.c.d.e.f
時,個人函數寫起來就很是複雜,我必須在這個函數的頭部驗證state
對象有沒有那個屬性。這是讓開發者很是頭疼的一件事。因而有了CombineReducers
。咱們除去源碼校驗函數部分,從最終返回的大的Reducers
來看。安全
Note:
- FinalReducers : 經過
=== 'function'
校驗後的Reducers
.- FinalReducerKeys :
FinalReducers
的全部key
(與入參
Object
的key
區別:過濾了value
不爲function
的值)
// 返回一個function。該方法接收state和action做爲參數 return function combination(state = {}, action) { var hasChanged = false var nextState = {} // 遍歷全部的key和reducer,分別將reducer對應的key所表明的state,代入到reducer中進行函數調用 for (var i = 0; i < finalReducerKeys.length; i++) { var key = finalReducerKeys[i] var reducer = finalReducers[key] // CombineReducers入參Object中的Value爲reducer function,從這能夠看出reducer function的name就是返回給store中的state的key。 var previousStateForKey = state[key] // debugger var nextStateForKey = reducer(previousStateForKey, action) // 若是reducer返回undefined則拋出錯誤 if (typeof nextStateForKey === 'undefined') { var errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } // 將reducer返回的值填入nextState nextState[key] = nextStateForKey // 若是任一state有更新則hasChanged爲true hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state }
combineReducers
實現方法很簡單,它遍歷傳入的reducers
,返回一個新的reducer
.該函數根據State
的key
去執行相應的子Reducer
,並將返回結果合併成一個大的State
對象。
createStore
主要用於Store
的生成,咱們先整理看下createStore
具體作了哪些事兒。(這裏咱們看簡化版代碼)
const createStore = (reducer, initialState) => { // initialState通常設置爲null,或者由服務端給默認值。 // internal variables const store = {}; store.state = initialState; store.listeners = []; // api-subscribe store.subscribe = (listener) => { store.listeners.push(listener); }; // api-dispatch store.dispatch = (action) => { store.state = reducer(store.state, action); store.listeners.forEach(listener => listener()); }; // api-getState store.getState = () => store.state; return store; }
源碼角度,一大堆類型判斷先忽略,能夠看到聲明瞭一系列函數,而後執行了dispatch
方法,最後暴露了dispatch
、subscribe
……幾個方法。這裏dispatch
了一個init Action
是爲了生成初始的State
樹。
首先,說ThunkMiddleware
以前,也許有人會問,到底middleware
有什麼用?
這就要從action
提及。在redux
裏,action
僅僅是攜帶了數據的普通js
對象。action creator
返回的值是這個action
類型的對象。而後經過store.dispatch()
進行分發……
action ---> dispatcher ---> reducers
同步的狀況下一切都很完美……
若是遇到異步狀況,好比點擊一個按鈕,但願1秒以後顯示。咱們可能這麼寫:
function (dispatch) { setTimeout(function () { dispatch({ type: 'show' }) }, 1000) }
這會報錯,返回的不是一個action
,而是一個function
。這個返回值沒法被reducer
識別。
你們可能會想到,這時候須要在action
和reducer
之間架起一座橋樑……
固然這座橋樑就是middleware
。接下來咱們先看看最簡單,最精髓的ThunkMiddleware
的源碼
const thunkMiddleware = ({ dispatch, getState }) => { return next => action => { typeof action === 'function' ? action(dispatch, getState) : next(action) } }
很是之精髓。。。咱們先記住上述代碼,引出下面的ApplyMiddleware
介紹applyMiddleware
以前咱們先看下項目中store
的使用方法以下:
let step = [ReduxThunk, middleware, ReduxLogger] let store = applyMiddleware(...step)(createStore)(reducer) return store
經過使用方法能夠看到有3處柯里化函數的調用,applyMiddleware
函數Redux
最精髓的地方,成功的讓Redux
有了極大的可拓展空間,在action
傳遞的過程當中帶來無數的「反作用」,雖然這每每也是麻煩所在。 這個middleware
的洋蔥模型思想是從koa
的中間件拿過來的,用圖來表示最直觀。
咱們來看源碼:
const applyMiddleware = (...middlewares) => { return (createStore) => (reducer, initialState, enhancer) => { var store = createStore(reducer, initialState, enhancer) var dispatch var chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } // 每一個 middleware 都以 middlewareAPI 做爲參數進行注入,返回一個新的鏈。 // 此時的返回值至關於調用 thunkMiddleware 返回的函數: (next) => (action) => {} ,接收一個next做爲其參數 chain = middlewares.map(middleware => middleware(middlewareAPI)) // 並將鏈代入進 compose 組成一個函數的調用鏈 dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
applyMiddleware
函數第一次調用的時候,返回一個以createStore
爲參數的匿名函數,這個函數返回另外一個以reducer
,initialState
,enhancer
爲參數的匿名函數.咱們在使用方法中,分別能夠看到傳入的值。
結合一個簡單的實例來理解中間件以及洋蔥模型
// 傳入middlewareA const middlewareA = ({ dispatch, getState }) => { return next => action => { console.warn('A middleware start') next(action) console.warn('A middleware end') } } // 傳入多個middlewareB const middlewareB = ({ dispatch, getState }) => { return next => action => { console.warn('B middleware start') next(action) console.warn('B middleware end') } } // 傳入多個middlewareC const middlewareC = ({ dispatch, getState }) => { return next => action => { console.warn('C middleware start') next(action) console.warn('C middleware end') } }
當咱們傳入多個相似A,B,C的middleware
到applyMiddleware
後,調用
dispatch = compose(...chain)(store.dispatch)
結合場景而且執行compose
結果爲:
dispatch = middlewareA(middlewareB(middlewareC(store.dispatch)))
從中咱們能夠清晰的看到middleware
函數中的next
函數相互鏈接,這裏體現了compose
FP編程思想中代碼組合的強大做用。再結合洋蔥模型的圖片,不難理解是怎麼樣的一個工做流程。
最後咱們看結果,當咱們觸發一個store.dispath
的時候進行分發。則會先進入middlewareA
而且打印A start
而後進入next
函數,也就是middlewareB
同時打印B start
,而後觸發next
函數,這裏的next
函數就是middlewareC
,而後打印C start
,以後才處理dispath
,處理完成後先打印C end
,而後B end
,最後A end
。完成總體流程。
Redux applyMiddleware
機制的核心在於,函數式編程(FP)
的compose
組合函數,需將全部的中間件串聯起來。compose
對單參函數的使用,對每一箇中間件採用currying
的設計。同時,利用閉包原理作到每一箇中間件共享Store
。(middlewareAPI
的注入)Thank you for reading this record.