Redux是業內鼎鼎有名的狀態管理工具,聽說做者Dan Abramov就是憑藉這個做品在業內名聲大振的。一開始我覺得這個被廣爲流傳使用的庫代碼,必然十分複雜,於是一直沒有去研究它的設計與實現。直到有天心血來潮翻了下它的源碼,發現不只暴露出來的API少,內部的API也少,總共不超過10個的API加起來竟然不到六百行代碼。。。前端
本篇文章討論的Redux源碼是基於版本v4.0.1,只討論其中核心API的實現並簡單發表下對Redux框架發展的見解。首先,先上張Redux的數據流向圖。react
就不詳細的介紹這個架構了,簡單的說下這個數據流中Redux實現的部分和須要咱們實現的部分分別是哪些:git
Redux實現: store.dispatch
、store.subscribe
、combineReducers
、applyMiddleware
程序員
咱們實現: actionCreator
、reducer
github
本篇文章咱們只分析Redux實現部分的代碼編程
dispatch在store對象中,而store又由createStore生成,所以找dispatch方法得從createStore中翻起。打開createStore文件,咱們能夠看到createStore中內部的同名方法,即爲咱們所尋。redux
拋開頭部的判斷Action是否爲純對象,以及判斷調用dispatch時是否dispatch正在調用中(不容許reducer中再次調用dispatch,不然容易形成無限遞歸)。能夠看到dispatch直接調用了reducer,來得到一個新的state,並在得到了新state以後調用手動觸發監聽state改變的函數。數組
subscribe也在store對象中,翻閱createStore能夠發現同名函數subscribe。架構
邏輯很明顯將監聽的函數放入對應的數組中,再返回一個解除監聽的函數。app
isDispatching
:防止在中間件reducer中加入/解除監聽函數isSubscribed
:用於減小屢次解除監聽邏輯形成的性能損耗ensureCanMutateNextListeners
:這個函數內容是當currentListeners和nextListeners相同時,從currentListeners淺複製一份給nextListeners。由於dispatch後執行的是currentListeners中監聽器的內容,因此能夠理解爲在當前觸發的監聽函數中若是調用解除監聽函數要下一次再觸發時才能生效。function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
複製代碼
看完了store中的dispatch和subscribe已經看完了基本的redux骨架。剩下的都是給redux加強功能的。
咱們再看一個咱們組織reducer都會使用上的函數combineReducers。它的邏輯就在同名文件中
雖然代碼看起來挺多,可是一點都不復雜,裏面有很大一部分代碼是用來肯定,主要能夠當作兩個部分。一個部分是獲取reducer進行預處理並返回一個統一的reducer處理函數,另外一個部分就是實際調用reducer的處理邏輯了。(話外音:感受全部高階函數都能這麼分--!)
預處理除了判斷邏輯,真正的執行邏輯也只有兩步。一是將合併的reducer拷貝到一個const對象中(我想之因此這麼作是防止,reducer對象被意外修改了吧)。二是獲取合併的reducer的全部key,放入數組中以備後續處理。
調用執行部分的邏輯也很直白,直接遍歷全部reducer來得到對應reducer產生的新狀態。除這以外就是判斷是否狀態發生改變,若是不發生改變就反回原始state。關於這一步我以爲是爲了監聽函數判斷state是否發生變化,而nextState已經生成了應該沒有什麼優化的意義在其中了。
要看懂applyMiddleware須要結合着createStore的enhancer才行,由於applyMiddleware是特殊的enhancer。
能夠看到enhancer
接受createStore
做爲參數,而且返回的函數是一個接受dispatch
、preloadedState
(初始化的狀態)做爲參數的函數,而且調用後返回符合Redux規定API的store。
如今再來看applyMiddleware函數就能清楚它的函數簽名是爲了徹底符合enhancer的接口。咱們再看下applyMiddleware的內部邏輯:
要看懂applyMiddleware咱們還應該看看,它提供給外界編寫middleware的API是什麼樣的。下面的logger是官方提供的一個例子。next表明的是下一個中間件。
咱們能夠看到applyMiddleware前面的邏輯是在設計一個假的dispatch,爲了防止在dispatch中再次dispatch形成死循環。核心的代碼是我紅圈圈出來的部分,如今還差一個compose素材就集齊了。
compose方法的做用是讓參數函數逆序序執行並將結果做爲下一個函數的參數,對於compose(f, g, h)
會得出(...args) => f(g(h(...args)))
。
如今再來看applyMiddleware中的核心代碼,首先是使用map方法將middleware調用一遍,將返回值在組成一個數組。咱們看logger簽名,能夠發現這至關因而將參數getState注入其中,再返回傳入next參數的函數。
而後再將這一組函數通過compose組成一個鏈式調用,而後調用生成包裝好的dispatch函數。compose是逆序調用,即寫在前頭的函數後調用,寫後頭的先調用。然而這一步調用的函數實際上是做爲前一個函數的next參數,還記得嗎它是表明下一個中間件。在真正調用dispatch時仍是先執行第一個中間件的邏輯,順序又正回來了。順便說下做爲第一個參數傳入的dispatch函數會做爲最後的中間件被調用,高明高明!
關於redux源碼的分析就上面那麼多了,下面主要是經過git對源碼的追綜,簡述下本身對於redux這個框架發展的見解。
__時間:__2015-05-30
最初的代碼目錄結構爲:
- src
- redux
connect.js
createDispatcher.js
flux.js
- stores
CounterStore.js
index.js
- App.js
複製代碼
能夠看到最初的代碼是沒有脫離view的,而且靈感有flux的影子在其中。
__時間:__2015-06-02
而後到了0.2.0版本,目錄變成以下:
- src
createDispatcher.js
observers.js
performs.js
providers.js
index.js
複製代碼
雖然如今沒有了樣板文件,可是閱讀其readme文檔能夠發現它仍是根具體的view框架(react)綁定在一塊兒的。這個版本有了初步的數據流想法。
// We're gonna need some decorators
import React from 'react';
import { observes } from 'redux';
// Gonna subscribe it
@observes('CounterStore')
export default class Counter {
render() {
const { counter } = this.props; // injected by @observes
return (
<p> Clicked: {counter} times </p>
);
}
}
複製代碼
__時間:__2015-06-09
在此版本出現了與目前想類型的概念,已經出現了dispatch、getState方法。
- src
- utils
- components
createDispatcher.js
createRedux.js
index.js
Redux.js
複製代碼
其中的Redux與createDispatcher與如今的createStore概念相相似,可是尚未中間件和enhancer。
__時間:__2015-08-15
此版本與如今已經很類似,在這個版本中store概念改成了reducer,出現了createStore方法,雖然沒有出現enhancer,可是有了middleware的概念。而且將與react相掛鉤的內容剝離到了react-redux庫中。
- src
- util
createStore.js
index.js
複製代碼
首先不得不爲大佬的高產而歎服啊,不到3個月的時間內,就對代碼進行了十次較大的版本變更。。。
其次咱們能夠發現,做者一開始只是根據已有的狀態管理規範作的包裝。而後逐步迭代纔有了今天的redux。而且一開始狀態管理是跟react捆綁的,直到後來纔將它拆分到react-redux中。對於redux的接口開放也從簡單逐漸到強大(先有applyMiddleware後有enhancer)。不斷與時俱進,從15年開始一直更新到了19年,除了考慮與其餘框架的兼容性,還將目前出現的與其相關未徹底兼容的新特性(Symbol.observable)加入其中。
總結下redux的出現,不只僅是做者有着廣闊的視野(對react和狀態管理工具)、深入的認知(函數式編程),而且也由於他保持着熱心與時俱進。
公衆號二分之一程序員,分享前端、計算機基礎知識,歡迎關注 :)
複製代碼