簡介
隨著應用變得復雜,需要對 reducer 函數 進行拆分,拆分后的每一塊獨立負責管理 state 的一部分。
Redux 中的 combineReducers 能讓我們很方便地把多個 reducers 組合起來,成為一個新的 reducer。
原理
combineReducers輔助函數的作用是,把一個由多個不同 reducer 函作為 value 的 object,合并成一個最終的 reducer 函數,然后就可以對這個 reducer 調用 createStore
。
合并后的 reducer 可以調用各個子 reducer,并把它們的結果合并成一個 state 對象。state 對象的結構由傳入的多個 reducer 的 key 決定。
state的最終結構為:
{
reducer1: ...,
reducer2: ...
}
通過上述的講解我們可以知道,combineReducecrs的基本型態應該是這樣的:
function combineReducers(reducers) {
return function (state, action) { /*...*/ };
}
它接受 reducers 作為參數,然后返回一個標準的 reducer 函數。
下面我們就來看一下combineReducers的源碼解析:
import { ActionTypes } from './createStore'
import isPlainObject from 'lodash/isPlainObject'
import warning from './utils/warning'
//根據key和action生成錯誤信息
function getUndefinedStateErrorMessage(key, 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'
//判斷reducers是否為空數組
//判斷state是否是對象
//給state中存在而reducer中不存在的屬性添加緩存標識并警告
//...
}
//這個方法用于檢測用于組合的reducer是否是符合redux規定的reducer
function assertReducerSanity(reducers) {
Object.keys(reducers).forEach(key => {
const reducer = reducers[key]
//調用reducer方法,undefined為第一個參數
//使用前面說到過的ActionTypes.INIT和一個隨機type生成action作為第二個參數
//若返回的初始state為undefined,則這是一個不符合規定的reducer方法,拋出異常
//...
})
}
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}"`)
}
}
//finalReducers是過濾后的reducers,它的每一個屬性都是一個function
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let sanityError
//檢測每個reducer是否是符合標準的reducer
try {
assertReducerSanity(finalReducers)
} catch (e) {
sanityError = e
}
return function combination(state = {}, action) {
if (sanityError) {
throw sanityError
}
//如果不是成產環境,做一些警告判斷
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
const nextState = {} //下一個state樹
//遍歷所有reducers,然后將每個reducer返回的state組合起來生成一個大的狀態樹,所以任何action,redux都會遍歷所有的reducer
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)
//如果此reducer返回的新的state是undefined,拋出異常
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
//如果當前action對應的reducer方法執行完后,該處數據沒有變化,則返回原來的流程樹
return hasChanged ? nextState : state
}
}
源碼解釋:
- 這是一個標準的 reducer 函數,接受一個初始化狀態 和 一個 action 參數;
- 每次調用的時候會去遍歷
finalReducers
(有效的 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
。