Redux源碼淺析

Redux解決的問題

JavaScript 須要管理比任什麼時候候都要多的 state (狀態)編程

state 在何時,因爲什麼緣由,如何變化已然不受控制。redux

經過限制更新發生的時間和方式,Redux 試圖讓 state 的變化變得可預測。設計模式

Redux設計分析

三個原則

  • 單一數據源
  • state是隻讀的 不可寫(想要修改就必須按照redux的單向數據流邏輯來操做),是實現單向數據流的根本保障
  • 使用純函數來執行修改 純函數意味着依賴單一,咱們只須要派發一個用於描述state變化的action便可。 這讓時間旅行、記錄和熱更新成爲可能 儘量的簡化單向數據流,不須要魔法

流程圖

action => middleware => reducer(s) => Store閉包

功能設計

  • createStore(rootReducer, initStore, middleware). 建立store
  • applyMiddleware(...middlewares). 使用中間件 鏈式使用
  • compose(...fn). 嵌套函數
  • combineReducers(...reducer). 組合reducer
  • bindActionCreator(actionCreators, dispatch). 封裝多個action

這是redux提供的幾個關鍵文件和它們的做用,其實簡單他們提供的功能不難發現他們函數式編程的影子。redux裏面設計比較巧妙的點我的感受是在中間件裏。middleware在redux中被設計爲在action發起後,到達reducer以前的拓展點。咱們能夠利用middleware實現相似日誌記錄,錯誤定位或者路由,還有異步處理action這些操做。app

關鍵點分析

redux的源碼是比較典型的FP風格,掌握一些基本FP概念,再去閱讀redux源碼會輕鬆不少異步

  • 高階函數

Higher order functions can take functions as parameters and return functions as return values.函數式編程

接受函數做爲參數傳入,並能返回封裝後函數。函數

  • 科裏化

Currying > Currying is the technique of translating the evaluation of a function that takes multiple arguments into evaluating a sequence of functions, each with a single argument學習

是把接受多個參數的函數變換成接受一個單一參數(最初函數的第一個參數)的函數,而且返回接受餘下的參數並且返回結果的新函數的技術。 add(1,2,3) => add(1)(2)(3)this

  • Compose > Composes functions from right to left. 組合函數,將從右向左執行。

    compose(subtract,multiple,add)(200) 等同於 subtract(multiple(add(200)));

內部使用reduce,而不是直接嵌套。

單向數據流

store.dispatch(action) => middleware =>reducer => store

貼上實現單向數據流的關鍵源碼(部分刪減)

dispatch函數的實現 (/createStore.js)

每次咱們調用原生dispatch時,都會有這樣的流程,在上圖第五行裏dispatch函數將拿到的action交給reducer函數處理,這裏的 isDispatching變量用來控制在reducer函數執行過程當中不容許再次dispatch,這個過程用try/finally提供可靠性;第十行取得當前監聽器函數列表的快照,在for循環中依次執行,這裏執行也是有講究,並無直接 listener[i]()調用,而是採用了分割this的行爲逐個調用監聽器函數。總結這個dispatch函數關鍵點以下

  • 狀態位控制流程,在reducer過程當中不容許dispatch
  • 用快照的形式存儲監聽器列表,避免在監聽器函數中調用subscribe函數引起的不可預期行爲
  • 隔離監聽器this,營造私有變量。

combineReducer函數的實現(/combineReducers.js)

  • reducer name 決定了state節點的key 調用由store提供的dispatch函數,便可觸發reducer,將返回的state更新,並觸發state監聽器列表中的方法。

中間件到底作了什麼

中間件發揮做用的時間點在派發action後,達到reducer前,能夠理解爲在調用原生dispatch(action)前,使用了中間件。 與其按照時間節點來理解,倒不如說中間件是爲了加強dispatch函數而作的設計 applyMiddleware的源碼很是精煉

帶着問題來閱讀源碼,中間件是如何實現如下幾點功能的

  • 如何讓中間件均可以獲取到state
  • 如何讓中間件可鏈式使用
  • 中間件的函數簽名爲何是middleware = store => next => action => { next(action) }
如何讓中間件均可以獲取到state

這裏聲明瞭一個middlewareAPI,經過裏面的getState方法就能夠拿到store裏的數據,另一個dispatch並無什麼實際的做用,就算調用了,它也會告訴你不能使用,這裏利用map將middlewareAPI傳入到每一箇中間件裏,構造了一個閉包,讓中間件能夠訪問到state數據,這裏也利用了currying函數延遲執行的特性,它接受了參數執行可是返回的是另一個待執行的函數。 如此就保證了每一箇中間件能夠獲取到state,關鍵點在於中間件科裏化的設計,讓其能夠延遲執行和參數複用。

const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } 
// 利用currying函數延遲執行的特性 
複製代碼
如何讓中間件可鏈式使用

如何將中間件串聯起來,並保存最後一個函數傳入的參數爲store.dispatch 想實現這個特性就要用到compose組合函數, 將中間件串聯起來,而且最後一個函數入參爲store.dispatch, 傳入的next就是下一個中間件,固然最後一個函數接受的next就是原生的store.dispatch,那個時候中間件就處理完畢,將action派發到reducer了。

const a = next => action => next(action) 
const b = next => action => next(action) 
const c = dispatch => action => dispatch(action) 
compose(c, b, a)(store.dispatch) 
// 源碼實現 dispatch = compose(...chain)(store.dispatch) 
複製代碼
中間件的函數簽名

函數簽名middleware = store => next => action => { next(action) }

其實看到這裏應該也大體明白了爲何要這麼設計中間件,返回的第一個函數是爲了保證中間件能夠取到全局狀態,返回的第二個函數是爲了保證中間件能夠依次調用。redux裏的中間件是一個科裏化的函數,其主要目的是爲了利用其延遲計算和參數複用的能力,來實現中間件的衆多特性。

一些遺憾

redux雖然爲咱們解決了state的管理問題,但依然不是百分之百的完美。邏輯上redux提供了一套簡單可行且很是清晰規範的state管理方案,數據的單數據流和其三個原則,與之帶來的是會寫一些模板代碼,若是使用了中間件,特別是redux-saga那種獨立規範特別多的中間件,會耗費咱們不少的時間在寫模板上,雖然咱們能夠對數據流動掌控的特別精細,可是時間成本依然減緩了咱們開發的效率。

改進方案

redux的改進應該在保留優點設計,解決痛點的基礎上進行。其實在多數開發者使用redux時通常會對其作簡單的封裝再使用,對redux增長一些設計模式或是使用企業內部的diapatch加強方法,這裏拋磚引玉,提出幾個redux理想改進的幾個須要注意的地方

  • 儘量保留redux的核心概念,下降學習成本
  • 減小redux模板代碼,能夠從提升複用性和提供內置模板的角度來減小開發者的重複工做
  • 能無縫接入redux的生態,支持中間件,enhancer
  • 保留redux的特性,保留其三個原則
  • 如何簡單抽象action和reducer之間的關係是一個很是重要的思考點
  • 提供對複雜場景的功能支持,好比動態增長reducer,提供多個store實例
相關文章
相關標籤/搜索