Redux 源碼解讀 & 中間件機制

前言

目前公司是基於React的技術棧,而我本身對於React的理解只限於應付項目業務開發而已,並且公司項目基本都是依賴umi框架機制,致使新人甚至對一些最基本的知識點都不瞭解就開始着手開發,好比react-router/react-reduxjavascript

大佬造的輪子用多了也是會有負面影響,爲了避免讓本身作一條只會用別人東西的鹹魚,決定去了解下redux的原理,由於redux源碼相對比較簡潔並且經過redux大體都能瞭解其餘的狀態管理工具的設計思路,一通則百通,無外乎這個道理,之後就算有幾百個輪子均可以很快理解java

工做流程圖

在瞭解源碼以前先能夠仔細看下Redux的總體流程圖(感謝若川大佬的圖,這裏冒昧借用下),若是能簡單看懂這張圖,那麼redux的源碼學習將會很是快速react

調試

爲了瞭解源碼,就必需要學會調試代碼,如今咱們去 redux@4.0.4 拷貝redux的代碼到本地git

git clone https://github.com/reduxjs/redux.git
複製代碼

能夠很清晰的看到redux是使用rollup打包,爲了能搞更好地調試,給它加上sourcemap配置github

//packages.json
"scripts": {
   "build": "rollup -c -m",
   ...
}
複製代碼

執行yarn build命令,能夠看到生成了es/dist/lib對應不一樣的環境編程

接下來我是直接使用的官方examples文件夾裏的async例子,直接把es文件複製進去,在examples/async/index.js 裏更改引入json

// import { createStore, applyMiddleware } from 'redux'
import { createStore, applyMiddleware } from './es/redux.js'
複製代碼

源碼理解

不少文章都是從createStore開始介紹,我按照本身的思路,從combineReducers開始一步一步理解redux的機制redux

combineReducers

先看下一個redux官方給出的簡單例子: /reducesc/index.jspromise

這裏定義了postsBySubreddit,selectedSubreddit兩個函數,即爲兩個reducer, 也是咱們項目中都會存在的,並且只會比它多。而在index.js中,須要把整合的rducer傳入createStore中,下面咱們看看 combineReducers 對這兩個reducer作了什麼bash

能夠很清晰得看到combineReducers獲取兩個reducer函數,通過簡單地校驗,生成了相似{ [function.name]: function }的鍵值對象, 並存放在閉包中(finalReducers), 最後返回了一個combination函數,用來傳入createStore中做爲形參,而combination具體作了什麼下面會講

createStore

reducer做爲參數傳入createStore中以後,createStore函數執行返回核心的store對象,順着思路來看下createStore中作了什麼

能夠很清楚的看到 store就是返回 dispatchsubscribegetStatereplaceReducer等方法。工做中用到過 redux的應該很是熟悉。

如今咱們具體看下對傳進來的reducer作了什麼,其實上面都是一些方法的定義,重點要注意這句

// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT })
複製代碼

這裏單獨執行一個distpatch方法,是爲了初始化生成一個state tree, 也就是生成一個包含store裏面全部state的樹形結構對象,接下來主要看下dispatch作了什麼, 而咱們以前傳入的reducer也就在這裏起了做用

dispatch

這裏咱們須要關注的重點就是currentReducer(其實就是上文傳過來的combination函數),這裏調用了這個函數,並傳入state/action

這裏能夠看出combination拿到最初的stateaction, 先作了簡單的判斷,以後也就是這裏的一個問題:遍歷執行全部的reducer來改變state

固然,最初的時候是造成了基本的樹形結構state, 可是以後的每一步dispatch都要去遍歷全部的reducer獲得新的state進行比較再返回最終的state, 保存進createStore裏的currentState中,能夠經過store暴露的getState方法獲取當前的state

subscribe

subscribe其實就是訂閱發佈模式的一種實現吧,我是這麼理解的,每當 dispatch action 的時候就會執行,state 樹中的一部分可能已經變化。你能夠在回調函數裏調用 getState() 來拿到當前 state。它的代碼也很簡單

咱們在index.js中加入,而後開始調試,它會走到createStore中暴露的subscribe方法裏

debugger;
store.subscribe(() => console.log(7))
複製代碼

subscribe方法會把listener加入到nextListeners中,至關於發佈訂閱模式中的消息隊列,以後在dispatch函數執行的時候,遍歷nextListeners出發listener函數,具體代碼在dispatch中查看,這裏不作詳述了

以上是對基本的redux機制作了簡單的分析理解,咱們會發現這些功能還不足以徹底承載咱們的業務,因此redux自己引入中間件的概念對自己功能進行擴展。社區有不少中間件的開源庫,包括redux-thunk,redux-promise,redux-saga等等等等,學是學不完的,但仍是那句話,一通百通的道理......

中間件機制

applyMiddleware

若是咱們要加入中間件,能夠在createStore中加入參數applyMiddleware(...middlewares), 這裏我以redux-thunk爲例

import { createStore, applyMiddleware } from './es/redux.js'
import thunk from 'redux-thunk'
import reducer from './reducers'

const store = createStore(
  reducer,
  applyMiddleware(thunk)
)
複製代碼

看一下applyMiddleware函數具體作了什麼

這裏applyMiddleware用了柯里化的理念,按上面的例子來講第一個參數就是thunk,獲取數據以後會在createStore裏調用

接下來看下createStore中對於中間件的處理邏輯

這裏的enhancer函數就是上方的applyMiddleware(thunk)返回函數,再次傳入createStore以及初始化的reducerpreloadedState, 這表示又再次走到了applyMiddleware函數裏面

這裏跟上面同樣生成了store對象,賦給middleware返回chain,這裏最難理解的就是compose方法,也是中間件機制的關鍵所在

compose

這裏其實也是函數式編程的一個組合的理念,把函數從右至左進行組合,能夠理解爲上個函數的返回就是下個函數的參數,若是仍是不明白這個reduce代碼可能不理解,那咱們慢慢來理解它

首先先看個例子:

很明顯結果是108, 執行順序依次是multiply,add,minus,那接下來咱們把它寫成通用的函數

redux中的compose跟這很相似,可是區別在於它返回的是雙層函數,即next(...)函數,這裏是中間件機制最難理解的地方,它就是經過這種方式來實現洋蔥模型的

爲了理解compose的組合機制,咱們先回來看下react-thunk的代碼

接來下咱們再本身實現一個簡單的middleware

在看下上面的調試的截圖,多箇中間件時,chain則等於[next => action => ..., next => action => ...], 再進入compose函數,獲得的結構就是(...args) => a(b(...args)), 這時又傳入store.dispatch(即next函數)

  1. 再觀察下這兩個中間件,store.dispatch傳入以後,next被替代爲store.dispatch 而返回了action => {}函數,這個函數又做爲下一個中間件的next函數
  2. 當用戶執行dispatch(action), 中間件接收到了action,則開始調用剛傳入的 next函數指向外部next直至store.dispatch

如今咱們根據redux-thunk具體的例子來理解:

export const requestPosts = subreddit => ({
  type: REQUEST_POSTS,
  subreddit
})

const fetchPosts = subreddit => (dispatch, getState) => {
  dispatch(requestPosts(subreddit))
}

// dispatch action in redux-thunk
dispatch(fetchPosts(subreddit))
複製代碼
  1. 根據react-thunk的代碼,fetchPosts(subreddit)的返回值就是action, 而action是函數, 則return action(dispatch, getState, extraArgument);, 根據例子上的第二個返回函數(dispatch, getState) => {}由此而來

  2. 這裏的action(dispatch, ...)中的dispatch則是compose組合返回的, requestPosts(subreddit)執行後再次進入react-thunk中間件,因爲不是函數則執行next(action),這時候若是有其餘的中間件,則根據compose繼續向左執行

總結

經過以上對Redux源碼的簡單解析,大體理解了它自己的內部機制, 如今再到頭部看那張流程圖是否理解得更加透徹了呢

參考資料

Redux 官方文檔

學習 redux 源碼總體架構,深刻理解 redux 及其中間件原理

redux-thunk

相關文章
相關標籤/搜索