# `combineReducers` 用法
## 核心概念
基于 Redux 的應用程序中最常見的 state 結構是一個簡單的 JavaScript 對象,它最外層的每個 key 中擁有特定域的數據。類似地,給這種 state 結構寫 reducer 的方式是分拆成多個 reducer,拆分之后的 reducer 都是相同的結構(state, action),并且每個函數獨立負責管理該特定切片 state 的更新。多個拆分之后的 reducer 可以響應一個 action,在需要的情況下獨立的更新他們自己的切片 state,最后組合成新的 state。
這個模式是如此的通用,Redux 提供了 `combineReducers` 去實現這個模式。這是一個高階 Reducer 的示例,他接收一個拆分后 reducer 函數組成的對象,返回一個新的 Reducer 函數。
在使用 `combineReducer` 的時候你需要重點注意下面幾個方法:
- 首先,`combineReducer` 只是一個工具函數,用于簡化編寫 Redux reducer 時最常見的場景。你沒有必要一定在你的應用程序中使用它,他不會處理每一種可能的場景。你完全可以不使用它來編寫 reducer,或者對于 `combinerReducer` 不能處理的情況編寫自定義的 reducer。(參見 [combineReducers](./BeyondCombineReducers.md) 章節中的例子和建議)
- 雖然 Redux 本身并不管你的 state 是如何組織的,但是 combineReducer 強制地約定了幾個規則來幫助使用者們避免常見的錯誤(參見 [combineReducer](../../api/combineReducers.md))
- 一個常見的問題是 Reducer 在 dispatch action 的時候是否調用了所有的 reducer。當初你可能覺得“不是”,因為真的只有一個根 reducer 函數啊。但是 `combineReducer` 確實有著這樣的特殊效果。在生成新的 state 樹時,`combinerReducers` 將調用每一個拆分之后的 reducer 和與當前的 Action,如果有需要的話會使得每一個 reducer 有機會響應和更新拆分后的 state。所以,在這個意義上, `combineReducers` 會調用所有的 reducer,嚴格來說是它包裝的所有 reducer。
- 你可以在任何級別的 reducer 中使用 `combineReducer`,不僅僅是在創建根 reducer 的時候。在不同的地方有多個組合的 reducer 是非常常見的,他們組合到一起來創建根 reducer。
## 定義 State 結構
這里有兩種方式來定義 Store state 的初始結構和內容。首先,`createStore` 函數可以將 `preloadedState` 作為第二個參數。這主要用于初始化那些在其他地方有持久化存儲的 state,例如瀏覽器的 localStorage,另外一種方式是當 state 是 `undefined` 的時候返回 initial state。這兩種方法在 [初始化 state 章節](./InitializingState.md) 中有著更加詳細的描述,但是在使用 `combineReducers` 的時候需要注意其他的一些問題。
`combineReducers` 接收拆分之后的 reducer 函數組成的對象,并且創建出具有相同鍵對應狀態對象的函數。這意味著如果沒有給 `createStore` 提供預加載 state,輸出 state 對象的 key 將由輸入的拆分之后 reducer 組成對象的 key 決定。這些名稱之間的相關性并不總是顯而易見的,尤其是在使用 ES6 的時候(如模塊的默認導出和對象字面量的簡寫時)。
這兒有一些如何用 ES6 中對象字面量簡寫方式使用 `combineReducers` 的例子。
```javascript
// reducers.js
export default (theDefaultReducer = (state = 0, action) => state)
export const firstNamedReducer = (state = 1, action) => state
export const secondNamedReducer = (state = 2, action) => state
// rootReducer.js
import { combineReducers, createStore } from 'redux'
import theDefaultReducer, {
firstNamedReducer,
secondNamedReducer
} from './reducers'
// 使用 ES6 的對象字面量簡寫方式定義對象結構
const rootReducer = combineReducers({
theDefaultReducer,
firstNamedReducer,
secondNamedReducer
})
const store = createStore(rootReducer)
console.log(store.getState())
// {theDefaultReducer : 0, firstNamedReducer : 1, secondNamedReducer : 2}
```
因為我們使用了 ES6 中的對象字面量簡寫方式,在最后的 state 中 key 的名字和 import 進來的變量的名字一樣,這可能并不是經常期望的,經常會對不熟悉 ES6 的人造成困惑。
同樣的,結果的名字也有點奇怪,在 state 中 key 的名字包含 “reducer” 這樣的詞通常不是一個好習慣,key 應該反映他們特有域或者數據類型。這意味著我們應該明確拆分之后 reducer 對象中 key 的名稱,定義輸出 state 對象中的 key,或者在使用對象字面量簡寫方式的時候,仔細的重命名的拆分之后的 reducer 以設置 key。
一個比較好用的使用示例如下:
```javascript
import { combineReducers, createStore } from 'redux'
// 將 default import 進來的名稱重命名為任何我們想要的名稱。我們也可以重命名 import 進來的名稱。
import defaultState, {
firstNamedReducer,
secondNamedReducer as secondState
} from './reducers'
const rootReducer = combineReducers({
defaultState, // key 的名稱和 default export 的名稱一樣
firstState: firstNamedReducer, // key 的名字是單獨取的,而不是變量的名字
secondState // key 的名稱和已經被重命名過的 export 的名稱一樣
})
const reducerInitializedStore = createStore(rootReducer)
console.log(reducerInitializedStore.getState())
// {defaultState : 0, firstState : 1, secondState : 2}
```
這種 state 的結構恰好能反應所涉及的數據,因為我們特別的設置了我們傳遞給 `combineReducers` 的 key。
- 自述
- 介紹
- 動機
- 核心概念
- 三大原則
- 先前技術
- 學習資源
- 生態系統
- 示例
- 基礎
- 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
- 排錯