在咱們開始學習源碼以前,咱們不妨先來看看何謂reducers:redux
如圖所見,咱們能夠明白, reducer
是用來對初始的狀態樹進行一些處理從而得到一個新的狀態樹的,咱們能夠繼續從其使用方法看看 reducer
到底如何作到這一點:api
function reducerDemo(state = {}, action) {
switch (action.type) {
case 'isTest':
return {
isTest: true
};
default:
return state;
}
}
複製代碼
從咱們的 reducerDemo
中,咱們能夠看到 reducer
接受了兩個參數:bash
經過對 action
中的 type
的判斷,咱們能夠用來肯定當前 reducer
是對指定 type
的 action
進行響應,從而對初始的 state
進行一些修改,得到修改以後的 state
的。從以前咱們在 createStore
中看到的狀況:數據結構
currentState = currentReducer(currentState, action)
複製代碼
每次 reducer
都會使用上一次的 state
,而後處理以後得到新的 state
。less
可是光是如此的話,在處理大型項目的時候咱們彷佛有點捉襟見肘,由於一個store
只能接受一個reducer
,在大型項目中咱們一般會有很是很是多的 action
用來對狀態樹進行修改,固然你也能夠在 reducer
中聲明海量的 switch...case..
來實現對單個action
的響應修改,可是當你這樣作的時候,你會發現你的reducer
愈來愈大,處理過程愈來愈複雜,各個業務邏輯之間的耦合度愈來愈高,最後你就會發現這個 reducer
將徹底沒法維護。dom
因此爲了解決在大型項目中的這類問題,咱們會使用多個reducer
,每一個reducer
會去維護本身所屬的單獨業務,可是正如咱們以前所說,每一個store
只會接受一個 reducer
,那咱們是如何將reducer一、reducer二、reducer三、reducer4
整合成一個reducer
而且返回咱們所需的狀態樹的呢?ide
固然咱們能想到的問題,redux
確定也能想到,因此他們提供了 combineReducers
api讓咱們能夠將多個 reducer
合併成一個 reducer
,並根據對應的一些規則生成完整的狀態樹,so,讓咱們進入正題,開始閱讀咱們 combineReducers
的源碼吧:函數
首先是combineReducers
的依賴,咱們能在代碼的頭部找到它:工具
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
複製代碼
能夠看到,combineReducers
僅僅依賴了以前咱們在上一篇文章中提到的工具類:post
進入正文,在combineReducers
的開始部分,咱們可以發現許多用於返回錯誤信息的方法:
function getUndefinedStateErrorMessage(key, action) {
const actionType = action && action.type
const actionDescription =
(actionType && `action "${String(actionType)}"`) || 'an action'
return (
`Given ${actionDescription}, reducer "${key}" returned undefined. ` +
`To ignore an action, you must explicitly return the previous state. ` +
`If you want this reducer to hold no value, you can return null instead of undefined.`
)
}
複製代碼
從方法可知,這個處理過程當中,咱們傳入了key(reducer的方法名)
以及action
對象,以後根據action中是否存在type
得到了action
的描述,最終返回了一段關於出現返回undefined
值的reducer
和action
的描述語以及提示。
function getUnexpectedStateShapeWarningMessage(
inputState,
reducers,
action,
unexpectedKeyCache
) {
const reducerKeys = Object.keys(reducers)
const argumentName =
action && action.type === ActionTypes.INIT
? 'preloadedState argument passed to createStore'
: 'previous state received by the reducer'
if (reducerKeys.length === 0) {
return (
'Store does not have a valid reducer. Make sure the argument passed ' +
'to combineReducers is an object whose values are reducers.'
)
}
if (!isPlainObject(inputState)) {
return (
`The ${argumentName} has unexpected type of "` + {}.toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] + `". Expected argument to be an object with the following ` +
`keys: "${reducerKeys.join('", "')}"`
)
}
const unexpectedKeys = Object.keys(inputState).filter(
key => !reducers.hasOwnProperty(key) && !unexpectedKeyCache[key]
)
unexpectedKeys.forEach(key => {
unexpectedKeyCache[key] = true
})
if (action && action.type === ActionTypes.REPLACE) return
if (unexpectedKeys.length > 0) {
return (
`Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
`"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
`Expected to find one of the known reducer keys instead: ` +
`"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
)
}
}
複製代碼
在說這段源碼以前,咱們須要稍微瞭解一下,當咱們使用combineReucers
,咱們傳入的reducer的數據結構:
function reducer1(state={}, action) {
switch (action.type) {
case 'xxx':
return true;
default:
return state;
}
}
function reducer2() {...}
function reducer3() {...}
function reducer4() {...}
const rootReducer = combineReucers({
reducer1,
reducer2,
reducer3,
reducer4
})
複製代碼
咱們傳入的時以reducer
的方法名做爲鍵,以其函數做爲值的對象,而使用rootReducer
生成的store
會是一樣以每一個reducer
的方法名做爲鍵,其reducer
處理以後返回的state
做爲值的對象,好比:
// 生成的state
{
reducer1: state1,
reducer2: state2,
reducer3: state3,
reducer4: state4
}
複製代碼
至於爲什麼會這樣,咱們後面再提,如今先讓咱們繼續往下閱讀這個生成錯誤信息的方法。
在這個方法中,其工做流程大概以下:
reducerKeys
獲取當前合併的reducer
的全部鍵值argumentName
獲取當前是否爲第一次初始化store
的描述reducer
的時候返回拋錯信息state
不是一個對象時,返回報錯信息。state
上未被reducer
處理的狀態的鍵值unexpectedKeys
,並將其存入cache
值中。replace action
,由於當使用store
的replaceReducer
時會自動觸發該內置action
,並將reducer
替換成傳入的,此時檢測的reducer
和原狀態樹必然會存在衝突,因此在這種狀況下檢測到的unexpectedKeys
並不具有參考價值,將不會針對性的返回拋錯信息,反之則會返回。經過如上流程,咱們將能對未被reducer
處理的狀態進行提示。
function assertReducerShape(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
const initialState = reducer(undefined, { type: ActionTypes.INIT })
if (typeof initialState === 'undefined') {
throw new Error(
`Reducer "${key}" returned undefined during initialization. ` +
`If the state passed to the reducer is undefined, you must ` +
`explicitly return the initial state. The initial state may ` +
`not be undefined. If you don't want to set a value for this reducer, ` + `you can use null instead of undefined.` ) } if ( typeof reducer(undefined, { type: ActionTypes.PROBE_UNKNOWN_ACTION() }) === 'undefined' ) { throw new Error( `Reducer "${key}" returned undefined when probed with a random type. ` + `Don't try to handle ${
ActionTypes.INIT
} or other actions in "redux/*" ` +
`namespace. They are considered private. Instead, you must return the ` +
`current state for any unknown actions, unless it is undefined, ` +
`in which case you must return the initial state, regardless of the ` +
`action type. The initial state may not be undefined, but can be null.`
)
}
})
}
複製代碼
相對以前的屢次判斷,這個就要簡單暴力的多了,直接遍歷全部的reducer
,首先經過傳入undefined
的初始值和內置的init action
,若是不能返回正確的值(返回了undefined值),那麼說明reducer
並無針對默認屬性返回正確的值,咱們將提供指定的報錯信息。
這以後又使用reducer
處理了undefined
初始值和內置隨機action
的狀況,這一步的目的是爲了排除用戶爲了不第一步的判斷,從而手動針對內置init action
進行處理,若是用戶確實作了這種處理,就拋出對應錯誤信息。
如此,咱們對combineReucers
的錯誤信息處理已經有了大概的瞭解,其大體功能以下:
reducer
是不是合規的reducer
不合規reducer
處理的狀態瞭解了這些以後,咱們即可以進入真正的combineReducers
了。
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const 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]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
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)
}
}
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
複製代碼
首先咱們看到變量聲明部分:
接下來,該方法循環遍歷了reducerKeys
,並在產品級(production)環境下對類型爲undefined
的reducer
進行了過濾和打印警告處理,其後又將符合規範的reducer
放到了finalReducer
中,這一步是爲了儘可能減小後面的流程受到空值reducer
的影響。
而後combineReducers
進一步的對這些非空reducer
進行了處理,檢測其中是否還有不合規範的reducer
(經過assertReducerShape
),並經過try catch
將這個錯誤存儲到shapeAssertionError
變量中。
正如咱們一直所說,reducer
須要是一個function
,因此咱們的combineReducer
將是一個高階函數,其會返回一個新的reducer
,也就是源碼中的combination
。
在返回的combination
中,會檢測是否有shapeAssertionError
,若是有調用該reducer
時將終止當前流程,拋出一個錯誤,而且在產品級環境下,還會檢測是否有未被reducer
處理的state
並打印出來進行提示(不中斷流程)。
最後纔是整個combination
的核心部分,首先其聲明瞭一個變量來標識當前狀態樹是否更改,並聲明瞭一個空的對象用來存放接下來會發生改變的狀態,而後其遍歷了整個finalReducer
,經過每一個reducer
處理當前state
,並將其得到的每一個值和以前狀態樹中的對應key值得狀態值進行對比,若是不一致,那麼就更新hasChanged
狀態,並將新的狀態值放到指定key
值得state
中,且更新整個狀態樹,固然其中仍是會對出現異常state
返回值的異常處理。
到此,咱們已經通讀了combineReducers
中的全部代碼,也讓咱們稍微對使用combineReducer
時須要注意的幾個點作一個總結:
reducer
必需要有非undefined
的返回值reducer
手動去操做內置的action
combineReducers
須要注意傳入的對象每一個鍵必須對應一個類型爲function
的reducer
(廢話請你們記住這幾個點,在這些前提下可以幫助你更快的理解咱們的combineReducers
感謝你的閱讀~