# `combineReducers` 進階
Redux 引入了非常實用的 `combineReducers` 工具函數,但我們卻有意將它限制于單一的應用場景:把不同片段的 state 的更新工作委托給一個特定的 reducer,以此更新由普通的 JavaScript 對象構成的 state 樹。它不解決 Immutable.js Maps 所構建的 state tree,也不會把其余部分的 state 作為額外參數傳遞給 reducer 或者排列 reducer 的調用順序,它同樣不關心 reducer 如何工作。
于是一個常見問題出現了,“`combineReducers` 如何處理這些應用場景呢?”通常給出的回答只是“你不能這么做,你可能需要通過其他方式解決”。**一旦你突破 `combineReducers` 的這種限制,就是創建各色各樣的“自定義” reducer 的時候了**,不管是為了解決一次性場景的特殊 reducer,還是能夠被廣泛復用的 reducer。本文為幾種典型的應用場景提供了建議,但你也可以自由發揮。
## 結合 Immutable.js 對象使用 reducers
由于目前 `combineReducers` 只能處理普通的 JavaScript 對象,對于把 Immutable.js Map 對象作為頂層 state 樹的應用程序來說,可能無法使用 `combineReducers` 管理應用狀態。因為很多開發者采用了 Immutable.js,所以涌現了大量提供類似功能的工具,例如 [redux-immutable](https://github.com/gajus/redux-immutable)。這個第三方包實現了一個能夠處理 Immutable Map 數據而非普通的 JavaScript 對象的 `combineReducers`。
## 不同 reducers 之間共享數據
類似地,如果 `sliceReducerA` 為了處理特殊的 action 正好需要來自 `sliceReducerB` 的部分 state 數據,或者 `sliceReducerB` 正好需要全部的 state 作為參數,單單就 `combineReducers` 是無法解決這種問題的。可以這樣來解決:把所需數據當額外參數的形式傳遞給自定義函數,例如:
```js
function combinedReducer(state, action) {
switch (action.type) {
case 'A_TYPICAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
b: sliceReducerB(state.b, action)
}
}
case 'SOME_SPECIAL_ACTION': {
return {
// 明確地把 state.b 作為額外參數進行傳遞
a: sliceReducerA(state.a, action, state.b),
b: sliceReducerB(state.b, action)
}
}
case 'ANOTHER_SPECIAL_ACTION': {
return {
a: sliceReducerA(state.a, action),
// 明確地把全部的 state 作為額外參數進行傳遞
b: sliceReducerB(state.b, action, state)
}
}
default:
return state
}
}
```
另一種解決“共享片段數據更新” (shared-slice updates) 問題的簡單方法是,給 action 添加額外數據。可以通過 thunk 函數或者類似的方法輕松實現,如下:
```js
function someSpecialActionCreator() {
return (dispatch, getState) => {
const state = getState()
const dataFromB = selectImportantDataFromB(state)
dispatch({
type: 'SOME_SPECIAL_ACTION',
payload: {
dataFromB
}
})
}
}
```
因為 B 的數據已經存在于 action 中,所以它的父級 reducer 不需要做任何特殊的處理就能把數據暴露給 `sliceReducerA`。
第三種方法是:使用 `combineReducers` 組合 reducer 來處理“簡單”的場景,每個 reducer 依舊只更新自己的數據;同時新加一個 reducer 來處理多塊數據交叉的“復雜”場景;最后寫一個包裹函數依次調用這兩類 reducer 并得到最終結果:
```js
const combinedReducer = combineReducers({
a: sliceReducerA,
b: sliceReducerB
})
function crossSliceReducer(state, action) {
switch (action.type) {
case 'SOME_SPECIAL_ACTION': {
return {
// 明確地把 state.b 作為額外參數進行傳遞
a: handleSpecialCaseForA(state.a, action, state.b),
b: sliceReducerB(state.b, action)
}
}
default:
return state
}
}
function rootReducer(state, action) {
const intermediateState = combinedReducer(state, action)
const finalState = crossSliceReducer(intermediateState, action)
return finalState
}
```
已經有一個庫 [reduce-reducers](https://github.com/acdlite/reduce-reducers) 可以簡化以上操作流程。它接收多個 reducer 然后對它們依次執行 `reduce()` 操作,并把產生的中間值依次傳遞給下一個 reducer:
```js
// 與上述手動編寫的 `rootReducer` 一樣
const rootReducer = reduceReducers(combinedReducers, crossSliceReducer)
```
值得注意的是,如果你使用 `reduceReducers` 你應該確保第一個 reducer 能夠定義初始狀態的 state 數據,因為后續的 reducers 通常會假定 state 樹已經存在,也就不會為此提供默認狀態。
## 更多建議
再次強調,Reducer *只是*普通的函數,明確這一概念非常重要。`combineReducers` 雖然實用也只是冰山一角。除了用 switch 語句編寫函數,還可以用條件邏輯;函數不僅可以彼此組合,也可以調用其他函數。也許你可能需要這樣的一個 reducer,它既能夠重置 state,也能夠響應特定的 action。你可以這樣做:
```js
const undoableFilteredSliceA = compose(
undoReducer,
filterReducer('ACTION_1', 'ACTION_2'),
sliceReducerA
)
const rootReducer = combineReducers({
a: undoableFilteredSliceA,
b: normalSliceReducerB
})
```
注意 `combineReducers` 無需知道也不關心任何一個負責管理 `a` 數據的 reducer。所以我們并不需要像以往一樣修改 `combineReducers` 來實現撤銷功能 —— 我們只需把各種函數組合成一個新函數即可。
另外 `combineReducers` 只是 Redux 內置的 reducer 工具,大量形式各異的可復用的第三方 reducer 工具層出不窮。在 [Redux Addons 目錄](https://github.com/markerikson/redux-ecosystem-links)中列舉了很多可供使用的第三方工具。也許這些工具解決不了你的應用場景,但你隨時都可以實現一個能夠滿足你需求的工具函數。
- 自述
- 介紹
- 動機
- 核心概念
- 三大原則
- 先前技術
- 學習資源
- 生態系統
- 示例
- 基礎
- Action
- Reducer
- Store
- 數據流
- 搭配 React
- 示例:Todo List
- 高級
- 異步 Action
- 異步數據流
- Middleware
- 搭配 React Router
- 示例:Reddit API
- 下一步
- 技巧
- 配置 Store
- 遷移到 Redux
- 使用對象展開運算符
- 減少樣板代碼
- 服務端渲染
- 編寫測試
- 計算衍生數據
- 實現撤銷重做
- 子應用隔離
- 組織 Reducer
- Reducer 基礎概念
- Reducer 基礎結構
- Reducer 邏輯拆分
- Reducer 重構示例
- combineReducers 用法
- combineReducers 進階
- State 范式化
- 管理范式化數據
- Reducer 邏輯復用
- 不可變更新模式
- 初始化 State
- 結合 Immutable.JS 使用 Redux
- 常見問題
- 綜合
- Reducer
- 組織 State
- 創建 Store
- Action
- 不可變數據
- 代碼結構
- 性能
- 設計哲學
- React Redux
- 其它
- 排錯
- 詞匯表
- API 文檔
- createStore
- Store
- combineReducers
- applyMiddleware
- bindActionCreators
- compose
- react-redux 文檔
- API
- 排錯