解析 Redux 源碼

解析 Redux 源碼

TIP

Redux 是 JavaScript 狀態容器,提供可預測化的狀態管理。

作為 React 全家桶的一份子,Redux 可謂說也是名聲響響,在 2016 年學習 JavaScript 想必沒有多少人沒聽過吧。

這裡,本文不是來教大家如何使用 Redux 的 API 的,這一類的文章已經很多,對於 Redux 的介紹和學習可以點擊下列鏈接:

  • 官方文檔

  • GitHub - reactjs/redux: Predictable state container for JavaScript apps

  • 自述 | Redux 中文文檔 Join the chat at https://gitter.im/camsong/redux-in-chinese

  • redux 大法好 —— 入門實例 TodoList(本人的渣渣文章

Redux 體小精悍(只有2kB)且沒有任何依賴,因此本文想通過閱讀 Redux 的源碼來學習 Redux 的使用以及思想。

源碼結構

Redux 的源碼結構很簡單,我們可以直接看 `src` 目錄下的代碼:

.src├── utils #工具函數├── applyMiddleware.js├── bindActionCreators.js ├── combineReducers.js ├── compose.js ├── createStore.js └── index.js #入口 js

index.js

這個是整個代碼的入口:

import createStore from ./createStoreimport combineReducers from ./combineReducersimport bindActionCreators from ./bindActionCreatorsimport applyMiddleware from ./applyMiddlewareimport compose from ./composeimport warning from ./utils/warningfunction isCrushed() {}if ( process.env.NODE_ENV !== production && typeof isCrushed.name === string && isCrushed.name !== isCrushed) { warning( 。。。 )}export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose}

這裡的 `isCrushed` 函數主要是為了驗證在非生產環境下 Redux 是否被壓縮(因為如果被壓縮了那麼 `(isCrushed.name !== isCrushed)` 就是 `true`),如果被壓縮會給開發者一個 warn 提示)。

然後就是暴露 `createStore` `combineReducers` `bindActionCreators` `applyMiddleware` `compose` 這幾個介面給開發者使用,我們來逐一解析這幾個 API。

createStore.js

這個是 Redux 最主要的一個 API 了,它創建一個 Redux store 來以存放應用中所有的 state,應用中應有且僅有一個 store。

import isPlainObject from lodash/isPlainObjectimport $$observable from symbol-observable// 私有 actionexport var ActionTypes = { INIT: @@redux/INIT}export default function createStore(reducer, preloadedState, enhancer) { // 判斷接受的參數個數,來指定 reducer 、 preloadedState 和 enhancer if (typeof preloadedState === function && typeof enhancer === undefined) { enhancer = preloadedState preloadedState = undefined } // 如果 enhancer 存在並且適合合法的函數,那麼調用 enhancer,並且終止當前函數執行 if (typeof enhancer !== undefined) { if (typeof enhancer !== function) { throw new Error(Expected the enhancer to be a function.) } return enhancer(createStore)(reducer, preloadedState) } if (typeof reducer !== function) { throw new Error(Expected the reducer to be a function.) } // 儲存當前的 currentReducer var currentReducer = reducer // 儲存當前的狀態 var currentState = preloadedState // 儲存當前的監聽函數列表 var currentListeners = [] // 儲存下一個監聽函數列表 var nextListeners = currentListeners var isDispatching = false // 這個函數可以根據當前監聽函數的列表生成新的下一個監聽函數列表引用 function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } ... getState ... ... subscribe ... ... dispatch ... ... replaceReducer ... ... observable ... dispatch({ type: ActionTypes.INIT }) return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable }}

首先定義了一個 `ActionTypes` 對象,它是一個 action,是一個 Redux 的私有 action,不允許外界觸發,用來初始化 Store 的狀態樹和改變 reducers 後初始化 Store 的狀態樹。

createStore

然後著重來看 createStore 函數:

1. 接受

它可以接受三個參數,reducerpreloadedStateenhancer

  • reducer:是一個函數,返回下一個狀態,接受兩個參數:當前狀態 和 觸發的 action;

  • preloadedState:初始狀態對象,可以很隨意指定,比如服務端渲染的初始狀態,但是如果使用 combineReducers 來生成 reducer,那必須保持狀態對象的 key 和 combineReducers 中的 key 相對應;

  • enhancer:store 的增強器函數,可以指定為 第三方的中間件,時間旅行,持久化 等等,但是這個函數只能用 Redux 提供的 applyMiddleware 函數來生成;

根據傳入參數的個數和類型,判斷 reducerpreloadedStateenhancer

2. 返回

調用完函數它返回的介面是 dispatchsubscribegetStatereplaceReducer [$$observable]

這也是我們開發中主要使用的幾個介面。

3. enhancer

如果 enhancer 參數傳入並且是個合法的函數,那麼就是調用 enhancer 函數(傳入 createStore 來給它操作),enhancer 函數返回的也是一個函數,在這裡傳入 reducer 和 preloadedState,並且返回函數調用結果,終止當前函數執行;

在 enhancer 函數裡面是如何操作使用的可以看 applyMiddleware 部分;

4. getState

function getState() { return currentState}

這個函數可以獲取當前的狀態,createStore 中的 currentState 儲存當前的狀態樹,這是一個閉包,這個參數會持久存在,並且所有的操作狀態都是改變這個引用,getState 函數返回當前的 currentState。

5. subscribe

function subscribe(listener) { // 判斷傳入的參數是否為函數 if (typeof listener !== function) { throw new Error(Expected listener to be a function.) } var isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } isSubscribed = false ensureCanMutateNextListeners() var index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) }}

這個函數可以給 store 的狀態添加訂閱監聽函數,一旦調用 `dispatch` ,所有的監聽函數就會執行;

`nextListeners` 就是儲存當前監聽函數的列表,調用 `subscribe`,傳入一個函數作為參數,那麼就會給 `nextListeners` 列表 `push` 這個函數;

同時調用 `subscribe` 函數會返回一個 `unsubscribe` 函數,用來解綁當前傳入的函數,同時在 `subscribe` 函數定義了一個 `isSubscribed` 標誌變數來判斷當前的訂閱是否已經被解綁,解綁的操作就是從 `nextListeners` 列表中刪除當前的監聽函數。

6. dispatch

function dispatch(action) { if (!isPlainObject(action)) { throw new Error( Actions must be plain objects. + Use custom middleware for async actions. ) } // 判斷 action 是否有 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.) } // 對拋出 error 的兼容,但是無論如何都會繼續執行 isDispatching = false 的操作 try { isDispatching = true // 使用 currentReducer 來操作傳入 當前狀態和action,放回處理後的狀態 currentState = currentReducer(currentState, action) } finally { isDispatching = false } var listeners = currentListeners = nextListeners for (var i = 0; i < listeners.length; i++) { var listener = listeners[i] listener() } return action}

這個函數是用來觸髮狀態改變的,他接受一個 action 對象作為參數,然後 reducer 根據 action 的屬性 以及 當前 store 的狀態來生成一個新的狀態,賦予當前狀態,改變 store 的狀態;

即 `currentState = currentReducer(currentState, action)`;

這裡的 `currentReducer` 是一個函數,他接受兩個參數:當前狀態 和 action,然後返回計算出來的新的狀態;

然後遍歷 `nextListeners` 列表,調用每個監聽函數;

7. replaceReducer

function replaceReducer(nextReducer) { // 判斷參數是否是函數類型 if (typeof nextReducer !== function) { throw new Error(Expected the nextReducer to be a function.) } currentReducer = nextReducer dispatch({ type: ActionTypes.INIT })}

這個函數可以替換 store 當前的 reducer 函數,首先直接把 `currentReducer = nextReducer`,直接替換;

然後 `dispatch({ type: ActionTypes.INIT })` ,用來初始化替換後 reducer 生成的初始化狀態並且賦予 store 的狀態;

observable

function observable() { var outerSubscribe = subscribe return { subscribe(observer) { if (typeof observer !== object) { throw new TypeError(Expected the observer to be an object.) } function observeState() { if (observer.next) { observer.next(getState()) } } observeState() var unsubscribe = outerSubscribe(observeState) return { unsubscribe } }, [$$observable]() { return this } }}

對於這個函數,是不直接暴露給開發者的,它提供了給其他觀察者模式/響應式庫的交互操作,具體可看 GitHub - tc39/proposal-observable: Observables for ECMAScript。

8. last

最後執行 `dispatch({ type: ActionTypes.INIT })`,用來根據 reducer 初始化 store 的狀態。

compose.js

`compose` 可以接受一組函數參數,從右到左來組合多個函數,然後返回一個組合函數;

export default function compose(...funcs) { if (funcs.length === 0) { return arg => arg } if (funcs.length === 1) { return funcs[0] } const last = funcs[funcs.length - 1] const rest = funcs.slice(0, -1) return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))}

applyMiddleware.js

export default function applyMiddleware(...middlewares) {

// 這個返回的函數就是 enhancer,接受 createStore 函數,再返回一個函數,接受的其實只有 reducer 和 preloadedState;

return (createStore) => (reducer, preloadedState, enhancer) => { var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] // 暴漏 getState 和 dispatch 給 第三方中間價使用 var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } // 創造第三方中間件使用 middlewareAPI 後返回的函數組成的數組 chain = middlewares.map(middleware => middleware(middlewareAPI)) // 結合這一組函數 和 dispatch 組成的新的 dispatch,然後這個暴漏給用戶使用,而原有的 store.dispatch 是不變的,但是不暴漏 dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } }}

`applyMiddleware` 函數的作用是組合 多個 中間件等等,然後返回一個函數(`enhancer`)

還記得在 createStore 中的一段嗎:

if (typeof enhancer !== undefined) { if (typeof enhancer !== function) { throw new Error(Expected the enhancer to be a function.) } return enhancer(createStore)(reducer, preloadedState)}

這裡 `enhancer === applyMiddleware(...)`;

然後再執行 `enhancer(createStore)` 繼續之後的操作;

這裡 `enhancer(createStore) 等同於 (reducer, preloadedState, enhancer) => { ... }`

然後再執行 `enhancer(createStore)(reducer, preloadedState)`;

再回到 `applyMiddleware` ,這裡調用了 `var store = createStore(reducer, preloadedState, enhancer)` ;

如上所見,這裡執行的時候已經沒有 `enhancer` 參數了,因此會再次執行 `createStore` 函數的全部部分,然後得到一個返回的實例 `store`;

之後會生成一個新的 `dispatch` ,先保存下來原生的 `dispatch` : `var dispatch = store.dispatch`;

var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action)}

這一步是把 store 的 `getState` 和 `dispatch` 介面暴露給中間件來操作: `chain = middlewares.map(middleware => middleware(middlewareAPI))`;

最後組合 全部中間件的返回值(函數)`chain` 和 `store.dispatch`,然後返回新的 `dispatch` : `dispatch = compose(...chain)(store.dispatch)`

這裡的 `dispatch` 並不是原有的 store 的,而是經過組合中間件之後新的 `dispatch`;

最後返回暴露給用戶的介面:

return { ...store, dispatch}

主要還是 store 原有的介面,但是用新的 dispatch 替換了原有的;

這個函數其實就是根據中間件和store的介面生成新的 dispatch 然後暴露給用戶。

combineReducers.js

這個函數可以組合一組 reducers(對象) ,然後返回一個新的 reducer 函數給 `createStore` 使用。

它接受一組 reducers 組成的對象,對象的 key 是對應 value(reducer函數)的狀態名稱;

比如: `{ userInfo: getUserInfo, list: getList }`

`userInfo` 是根據 `getUserInfo` 函數計算出來的;

那麼 store 裡面的 state 結構就會是: `{ userInfo: ..., list: ... }`

export default function combineReducers(reducers) { // 根據 reducers 生成最終合法的 finalReducers:value 為 函數 var reducerKeys = Object.keys(reducers) var finalReducers = {} for (var i = 0; i < reducerKeys.length; i++) { var key = reducerKeys[i] if (process.env.NODE_ENV !== production) { if (typeof reducers[key] === undefined) { warning(`No reducer provided for key "${key}"`) } } if (typeof reducers[key] === function) { finalReducers[key] = reducers[key] } } var finalReducerKeys = Object.keys(finalReducers) if (process.env.NODE_ENV !== production) { var unexpectedKeyCache = {} } // 驗證 reducer 是否合法 var sanityError try { assertReducerSanity(finalReducers) } catch (e) { sanityError = e } // 返回最終生成的 reducer return function combination(state = {}, action) { if (sanityError) { throw sanityError } if (process.env.NODE_ENV !== production) { var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache) if (warningMessage) { warning(warningMessage) } } var hasChanged = false var nextState = {} for (var i = 0; i < finalReducerKeys.length; i++) { var key = finalReducerKeys[i] var reducer = finalReducers[key] var previousStateForKey = state[key] var nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey === undefined) { var errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } // 遍歷一遍看是否改變,然後返回原有狀態值或者新的狀態值 return hasChanged ? nextState : state }}

最終返回一個 `combination` 也就是真正傳入 `createStore` 的 reducer 函數;

這是一個標準的 reducer 函數,接受一個初始化狀態 和 一個 action 參數;

每次調用的時候會去遍歷 `finalReducer` (有效的 reducer 列表),獲取列表中每個 reducer 對應的先前狀態: `var previousStateForKey = state[key]`;

看到這裡就應該明白傳入的 `reducers` 組合為什麼 `key` 要和 store 裡面的 state 的 `key` 相對應;

然後得到當前遍歷項的下一個狀態: `var nextStateForKey = reducer(previousStateForKey, action)` ;

然後把它添加到整體的下一個狀態: `nextState[key] = nextStateForKey`

每次遍歷會判斷整體狀態是否改變: `hasChanged = hasChanged || nextStateForKey !== previousStateForKey`

在最後,如果沒有改變就返回原有狀態,如果改變了就返回新生成的狀態對象: `return hasChanged ? nextState : state`。

bindActionCreators.js

`bindActionCreators` 函數可以生成直接觸發 action 的函數;

實質上它只說做了這麼一個操作 `bindActionFoo = (...args) => dispatch(actionCreator(...args))`

因此我們直接調用 `bindActionFoo` 函數就可以改變狀態了;

接受兩個參數,一個是 `actionCreators(` actionCreator 組成的對象,key 對於生成的函數名/或者是一個 actionCreator ),一個是 `dispatch,` store 實例中獲取;

function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args))}export default function bindActionCreators(actionCreators, dispatch) { // 是一個函數,直接返回一個 bindActionCreator 函數,這個函數調用 dispatch 觸發 action if (typeof actionCreators === function) { return bindActionCreator(actionCreators, dispatch) } if (typeof actionCreators !== object || actionCreators === null) { throw new Error( `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? null : typeof actionCreators}. ` + `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` ) } // 遍歷對象,然後對每個遍歷項的 actionCreator 生成函數,將函數按照原來的 key 值放到一個對象中,最後返回這個對象 var keys = Object.keys(actionCreators) var boundActionCreators = {} for (var i = 0; i < keys.length; i++) { var key = keys[i] var actionCreator = actionCreators[key] if (typeof actionCreator === function) { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators}

總結

閱讀了一遍 Redux 的源碼,實在是太精妙了,少依賴,少耦合,純函數式。

tip

也歡迎大家關注我的博客: qiutc.me/


推薦閱讀:

我對Flexbox布局模式的理解
從0到1,細說 React 開發環境搭建那點事
Immutable 詳解及 React 中實踐
React 組件如何與其他框架進行數據交流?
【譯】Redux + React 應用程序架構的 3 條規範(內附實例)

TAG:前端开发 | React | Redux |