背景
因為就得去實習了。所以打算開始補補坑。比如自己閱讀源碼的計劃。所以今天來聊聊redux的源碼。后續會有redux-thunk和react-redux的源碼閱讀。搞定這些的話,就開始閱讀一個node的庫的源碼了,比如eventproxy和anywhere。
開始
-
總覽, redux的文件結構
文件看起來貌似不少,其實,要理解redux的內部實現,主要就看 createStore.js
,applyMiddleware.js ,combineReducers.js和compose.js。下面從createStore.js開始看。
-
createStore.js
export default function createStore(reducer, preloadedState, enhancer) {// 如果第二個參數沒有傳入初始的state,而是傳入了enhancer(為applyMiddleware調用的返回值), 那就將第二個參數,即preloadedState賦值給enhancerif (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {enhancer = preloadedStatepreloadedState = undefined}// 如果傳入了enhancer,但enhancer不是一個函數,報錯if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}// 反之, 執行。注意此處。此處意味著,如果createStore的時候傳入了enhancer,是會將createStore傳入enhancer中,執行enhancer, 而enhancer的返回值也是一個函數。具體的可以等到下面我們講解applyMiddleware,看完你就知道到底發生了什么。return enhancer(createStore)(reducer, preloadedState)}// 如果沒傳入enhancer,就繼續下面的邏輯// reducer是要求為一個函數的,如果不是一個函數,報錯if (typeof reducer !== 'function') {throw new Error('Expected the reducer to be a function.')} ..........// 最后createStore就會返回dispatch,subscribe, getState等幾個常用的apireturn {dispatch,subscribe,getState,replaceReducer,[$$observable]: observable}; } 復制代碼
?
上面的代碼給大家展覽了下createStore這個函數大概做了什么,其實就是封裝了一些api,最后暴露給用戶使用。接下來看一下各個api的實現:
先看一下私有變量的定義
let currentReducer = reducer // 就是reducer嘛let currentState = preloadedState // 就是傳入的初始state嘛let currentListeners = [] // 當前的監聽器隊列let nextListeners = currentListeners // 未來的監聽器隊列let isDispatching = false // 標志是否正在dispatch 復制代碼
getState : 用來獲取store中的state的。因為redux是不允許用戶直接操作state,對于state的獲取,是得通過getState的api來獲取store內部的state。
function getState() {// 如果正在dispatch的話, 說明新的state正在計算中,現在的state是舊的,為了確保用戶能獲得新的// state,所以要加一個判斷,如果是正在dispatch的話,就報錯,反之,返回現在的stateif (isDispatching) {throw new Error('You may not call store.getState() while the reducer is executing. ' +'The reducer has already received the state as an argument. ' +'Pass it down from the top reducer instead of reading it from the store.')}return currentState} 復制代碼
subscribe :redux提供了用戶一個監聽state變化的api,這個尤為重要,如果沒有這個api的暴露,react-redux應該就比較實現了。
function subscribe(listener) {// listener是state變化時的回調,必須是個函數if (typeof listener !== 'function') {throw new Error('Expected the listener to be a function.')}// 如果是正在dispatch中,就報錯。因為要確保state變化時,監聽器的隊列也必須是最新的。所以監聽器的注冊要在計算新的state之前。if (isDispatching) {throw new Error('You may not call store.subscribe() while the reducer is executing. ' +'If you would like to be notified after the store has been updated, subscribe from a ' +'component and invoke store.getState() in the callback to access the latest state. ' +'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.')}// 標志是否注冊,額,其實個人感覺沒啥必要。不過仔細想想,應該是防止用戶多次調用取消監聽的函數。let isSubscribed = true// 其實這個函數就是判斷當前的監聽器隊列和未來的是否一樣,如果不一樣那就將當前的賦值給未來的,額,還是不是很理解為什么得這么實現,可能是為了達到數據不可變的效果,避免壓進新的回調時,導致當前的監聽器隊列也有這個回調ensureCanMutateNextListeners()// 將回調壓進未來的監聽器隊列中nextListeners.push(listener)// 注冊監聽器后會返回一個取消監聽的函數return function unsubscribe() {// 如果是已經調用該函數取消監聽了,就返回if (!isSubscribed) {return}if (isDispatching) {throw new Error('You may not unsubscribe from a store listener while the reducer is executing. ' +'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.')}// 標志已經取消了isSubscribed = falseensureCanMutateNextListeners()// 刪除const index = nextListeners.indexOf(listener)nextListeners.splice(index, 1)}} 復制代碼
dispatch : 該函數是與getState對應的,getState是讀,那dispatch就是寫。redux的改動state,只能通過發起一個dispatch,傳達一個action給reducer,reducer會根據action和currentState以及自己的內部實現邏輯,來計算出新的state,從而達到寫的目的。
function dispatch(action) {// action要求是一個簡單對象,而一個簡單對象就是指通過對象字面量和new Object()創建的對象,如果不是就報錯。if (!isPlainObject(action)) {throw new Error('Actions must be plain objects. ' +'Use custom middleware for async actions.')}// reducer內部是根據action的type屬性來switch-case,決定用什么邏輯來計算state的,所以type屬性是必須的。if (typeof action.type === 'undefined') {throw new Error('Actions may not have an undefined "type" property. ' +'Have you misspelled a constant?')}// 如果是已經在dispatch的,就報錯,避免不一致if (isDispatching) {throw new Error('Reducers may not dispatch actions.')}// 這里就是計算新的state,并賦值給currentStatetry {isDispatching = truecurrentState = currentReducer(currentState, action)} finally {isDispatching = false}// state更新了后,就如之前我們所說的subscribe,將注冊的回調都觸發一遍。大家要注意這里,是都觸發一遍哦!這個點了解,react-redux的一些原理會比較容易理解。const listeners = (currentListeners = nextListeners)for (let i = 0; i < listeners.length; i++) {const listener = listeners[i]listener()}return action} 復制代碼
以上就是createStore的大致實現。這個函數難度不大,更多的是一個了解redux的入口。咱們從這個入口來一點點挖掘別的代碼的實現。下面先從combineReducers開始
-
combineReducers
- 這個函數是用來整合多個reducers的, 因為createStore只接受一個reducer。
- 這個函數分為兩部分,第一部分是檢驗用戶傳入的reducers的準確性。第二部分就是計算state的邏輯。第一部分大家看一看也就了解了為什么,咱們主要看看第二部分
export default function combineReducers(reducers) {................return function combination(state = {}, action) {if (shapeAssertionError) {throw shapeAssertionError}if (process.env.NODE_ENV !== 'production') {const warningMessage = getUnexpectedStateShapeWarningMessage(state,finalReducers,action,unexpectedKeyCache)if (warningMessage) {warning(warningMessage)}}// hasChanged來標志是否計算出了新的statelet hasChanged = false// 這個就是存儲新的state的const nextState = {}// emmmm, 就是遍歷每一個reducer,把action傳進去,計算statefor (let i = 0; i < finalReducerKeys.length; i++) {const key = finalReducerKeys[i]const reducer = finalReducers[key]const previousStateForKey = state[key]const nextStateForKey = reducer(previousStateForKey, action)// 如果某個reducer沒有返回新的state,就報錯if (typeof nextStateForKey === 'undefined') {const errorMessage = getUndefinedStateErrorMessage(key, action)throw new Error(errorMessage)}nextState[key] = nextStateForKey// 此處來判斷是否有新的statehasChanged = hasChanged || nextStateForKey !== previousStateForKey}// 根據該標志,決定返回原來的state, 還是新的statereturn hasChanged ? nextState : state} } 復制代碼
這個整合的過程就是將所有的reducer存在一個對象里。當dispatch一個action的時候,通過遍歷每一個reducer, 來計算出每個reducer的state, 其中用到的優化就是每遍歷一個reducer就會判斷新舊的state是否發生了變化, 最后決定是返回舊state還是新state。最后得到的state的數據結構類似存reducer的數據結構,就是鍵為reducer的名字,值為對應reducer的值。這個部分其實也不難。下面繼續,我們看看applyMiddleware的實現
-
applyMiddleware
這個部分就是用來擴展redux的功能的。因為redux的最原始的功能就是操作state,管理state。如果我們需要在這個基礎上,根據需求擴展一些功能,就需要通過使用別人編寫好的中間件,或者自己編寫的中間件來達到需求。比如,發起一個dispatch時,我們為了方便調試,不愿意每次自己手動console.log出這個action,這個時候編寫一個logger中間件,就可以自動打印出每次dispatch發起的action,就很容易方便我們測試。又比如,我們要處理異步的action,就可以使用redux-thunk和redux-saga這類中間件。總之,該函數為我們提供了無限的可能。
我們一點點來看代碼:
export default function applyMiddleware(...middlewares) {return createStore => (...args) => {const store = createStore(...args)............return {...store,dispatch}} 復制代碼
先看個總覽的,注意到applyMiddleware接受不定數量的中間件,然后返回一個接受一個creatStore作為參數,返回一個函數的函數。還記得我們在creatStore的時候么?那里有個場景就是
if (typeof enhancer !== 'undefined') {if (typeof enhancer !== 'function') {throw new Error('Expected the enhancer to be a function.')}return enhancer(createStore)(reducer, preloadedState)} 復制代碼
當enhancer不為空且為函數時,就執行該函數,并return回去,作為creatStore的返回值。而這個enhancer是什么呢?翻看redux的文檔:
const store = createStore(reducers, applyMiddleware(a, b, c)); 復制代碼
哦!enhancer就是applyMiddleware的結果,就是一個 creatStore => (...args) = {}的函數。那看下enhancer的代碼:
return createStore => (...args) => {const store = createStore(...args)// 1、也許有的同學一開始看到這個會有點蒙蔽, 我當時看到也是覺得奇怪, 這個dispatch的邏輯不對勁// 而且, 還把這個dispatch作為middleware的參數傳了進去,代表在中間件時使用dispatch的邏輯是這個// 但是看到下面, dispatch = compose(...chain)(store.dispatch)// 還行, 根據作用域鏈, 我們可以知道在中間件中調用dispatch的時候, 其實就是調用了這個dispatch, 而不是一開始聲明的邏輯// 而這個dispatch是已經經過compose的包裝的了.邏輯到這里的時候就很清楚了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)}// 2、compose是如何將中間件串聯在一起的?// 首先一個最簡單的中間件的格式: store => next => action => {}// 這一行代碼就是傳入了store, 獲得了 next => action => {} 的函數const chain = middlewares.map(middleware => middleware(middlewareAPI))// 這一行代碼其實拆分成兩行// const composeRes = compose(...chain);// dispatch = composeRes(store.dispatch);// 第一行是通過compose, 將一個 這樣 next => action => {} 的數組組合成 (...args) => f(g(b(...args))) 這么一個函數// 第二行通過傳入store.dispatch, 這個store.dispatch就是最后一個 next => action => {}的next參數// 傳入后 (...args) => f(g(b(...args)) 就會執行, 執行時, store.dispacth作為b的next傳入, b函數結果action => {}會作為// g的next傳入, 以此類推. 所以最后dispatch作為有中間件的store的dispatch屬性輸出, 當用戶調用dispatch時, 中間件就會一個一個// 執行完邏輯后, 將執行權給下一個, 直到原始的store.dispacth, 最后計算出新的statedispatch = compose(...chain)(store.dispatch)return {...store,dispatch}} 復制代碼
跟著上面的注釋,大家應該能弄懂enhancer的原理。我這里總結一下,enhancer接收一個creatStore,會在內部創建一個store,然后對該store進行增強,增強的部位在于dispatch。增強的具體方式是通過compose來構造一個dispatch鏈,鏈的具體形式就是**[中間件1,中間件2, ......, 中間件N, store.dispatch]** ,然后將增強的dispatch作為store新的dispatch暴露給用戶。那用戶每次dispatch的時候,就會依次執行每個中間件,執行完當前的,會將執行權交給下一個,直到reducer中,計算出新的state。
結語
網上講解redux的源碼很多,我這篇也是其中一個,主要是我個人學習源碼后的,一種記錄方式,加深自己印象,也為了之后忘了可以快速重溫。redux其實實現上不難,但是思想上真是精髓。程序員的編碼能力是一個剛需,但是設計思想是要借他山之玉,來攻石的。站在巨人的肩膀上看遠方,希望自己多閱讀他人的源碼,在能了解原理更好運用的同時,以后自己也能創造出好用的輪子。謝謝大家花時間觀看。另外,附源碼地址:github.com/Juliiii/sou… ,歡迎大家star和fork ,也歡迎大家和我討論 。