# `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
- 排错