learning Redux from todo[2]


接着说, 刚才我们说到了`创建动作`和`动作创建者`以及如何绑定和分发动作.

接下来我们看看` reducer`.

### Reducer

现在我们考虑的问题就是, 动作分发了之后呢, 到哪儿去,干了什么!?

关于`动作`, 它是实实在在定义了干什么. 但是它干什么怎么才能够影响到 app 的状态呢, 让 app 做出对应的响应呢, **这就是` reducer` 的要做的.**

####设计状态结构

先来说说` state`,在 redux 里所有的状态都是保持在一个单个的对象里,现在我们来想想一下她的样子.

拿` todo app `来说,我们要存储2样东西:

- todo 的 list
	- 这个是最主要的,时刻都能够取到完整的 todo 数据.
- 当前被选中的显示过滤器(要显示什么状态的 todo)
	- 这里我们只需要保存当前用户选中需要显式那个类型的 todo 就好,(如当前进行的或者已经完成的.)

可能对于一个 app 来说,我们通常下意识会认为有很多事情,数据需要处理. 有很多的 case. 但是对于这里所讲的`状态`来说, 我们需要抽象,取出最主要的东西. 

比如说, 我们只需要 care 这些数据就可以, 利用这些数据我们就能够正常运转 app


```
{
  visibilityFilter: 'SHOW_ALL',
  todos: [
    {
      text: 'Consider using Redux',
      completed: true,
    },
    {
      text: 'Keep all state in a single tree',
      completed: false
    }
  ]
}
```

> 在一个较复杂的 app 中,可能实体之间会相互引用.我们建议尽量把状态保持越简单越好. 不要有嵌套的关系存在. 
> 尽量然后每一个对象有一个 id 能够识别. 通过 id 来引用.想象 app 的状态就和 database 里的状态类似. 这个可以查看 [normalizr](https://github.com/gaearon/normalizr) 的详细文档来看.

#### 处理动作

上面我们大概知道了找个 todo app 里`状态`对象样子, 接下来我们写` reducer`. 

> 重要事情说三遍

**`reducer` 就是一个`纯函数`,接受2个参数, 第一个的当前的状态,第二个是动作, 然后返回最新的状态.**
**`reducer` 就是一个`纯函数`,接受2个参数, 第一个的当前的状态,第二个是动作, 然后返回最新的状态.**
**`reducer` 就是一个`纯函数`,接受2个参数, 第一个的当前的状态,第二个是动作, 然后返回最新的状态.**



```
(previousState, action) => newState
```

这个我就称之为` reducer`, 因为它其实是这样一个实现:

```
Array.prototype.reduce(reducer, ?initialValue)
```

对于` reducer` 来说保证它的[`纯洁性`](https://en.wikipedia.org/wiki/Pure_function)是非常重要的.在 reducer 里保证不能够发生如下的事情:

- 改变参数
- 不能够做其他事情如调用 API 或者修改 route
- 调用不 `pure` 的函数如 `Date.now()` 或者 `Math.random()`

这里先不讨论副作用的东西之后高阶时候会说. 现在我们只记住对于 reducer 的 function 必须是 pure 的. `给一样的参数,总是返回同样的结果,没有副作用,没有 api 调用,没有突变,只是一个计算`.

接下来我们一步步的来理解之前定义的` action`. 首选我们初始化`状态`. redux 会调用一个 reducer 而当前是` undefined` 状态. 此时就是一个机会来初始化 app 的状态.

```

import { VisibilityFilters } from './actions'

const initialState = {
  visibilityFilter = VisibilityFilters.SHOW_ALL,
  todos: []
}

function todoAPP(state, action) {
  if (typeof state === 'undefined'){
    return initialState
  }
  // For now, don't handle any actions
  // and just return the state given to us
  return state
}
```

上面代码里的关于` state` 可以用` es6`的`参数默认值`语法来写就是:

```
function todoAPP(state = initialState, action){
 return state
}
```

让我们来处理` SET_VISIBILITY_FILTER`, 我们只需要来修改这个` visibilityFilter` 的值:

```
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}
```

>注意

1. 我们这里不会修改` state`,而是通过` object.assign()` 函数来拷贝这个对象. 
	2. 而` Object.assgin(state, {visibilityFilyer: action.filter})`同样是错误的. 因为` state` 作为第一个参数传递, 它将要被修改. 你必须传递一个空的对象给第一个参数. 看[这里](http://redux.js.org/docs/recipes/UsingObjectSpreadOperator.html)
2. 我们同时把默认的初始状态作为默认返回值. 这个很重要一旦遇到未知的 action的情况下还是要返回状态.

注意:

> Object.assign()是 es6的一部分,目前还没有大部分实现支持.需要一个 polyfill 来支持.我们用 Babel 的一个插件.

#### 处理更多动作

```
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })    
    default:
      return state
  }
}
```

上面很明显我们都不是直接在修改` state`, 而是取而代之用一个新的 object 来接住之前的 state, 然后再 merge 最后的 todo, 这样做到不修改原有的状态.

最后` TOGGLE_TODO` 的动作也就很容易了:


```
case TOGGLE_TODO:
  return Object.assign({}, state, {
    todos: state.todos.map((todo, index) => {
      if (index === action.index) {
        return Object.assign({}, todo, {
          completed: !todo.completed
        })
      }
      return todo
    })
  })
```

我们希望要修改一个array 里特定的 ite m 而不突变.我们需要利用当前的 array创建一个新的 array, 而除了那个即将要被修改的. 而这样的动作似乎经常发生. 我们就可以接住帮助类如` react--addons-update`,`updeep` 来完成. 甚至有一个库可以干这样的事情, [Immutable(http://facebook.github.io/immutable-js/), 记住别去动 state.

### 分隔Reducers

```
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
      return Object.assign({}, state, {
        todos: [
          ...state.todos,
          {
            text: action.text,
            completed: false
          }
        ]
      })
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: state.todos.map((todo, index) => {
          if(index === action.index) {
            return Object.assign({}, todo, {
              completed: !todo.completed
            })
          }
          return todo
        })
      })
    default:
      return state
  }
}

```

目前看上去我们的代码类似这样的. 看上去` todos` 和` visibilityFilter` 他们是单独更新的.有时候` state` 是需要更加的独立的.但是这里可以吧 todo 的更新先单独到一个函数里:

```
function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    case ADD_TODO:
    case TOGGLE_TODO:
      return Object.assign({}, state, {
        todos: todos(state.todos, action)
      })
    default:
      return state
  }
}
```


注意, 这里的` todos` 定义也是接收一个` state`, 而它是一个 array. 现在` todoAPP` 给了它一段 state 让他来管理(也就是todo list). 而 `todos` **知道如何来更新这个片段**(怎么知道的?). **这个称作 复合reducer,而且它是构建redux app 的基础模式**.

让我们来看看这个**复合的 reducer**, 我们是否可以把对于 visibilityFilter 的管理也抽出来作为 reducer manager 呢, 可以的:


```

function visibilityFilter(state = SHOW_ALL, action){
  switch (action.type){
    case SET_VISIBILITY_FILTER:
      return action.filer
      
    default:
      reuturn state
  }
}
```

现在我们可以重写主 reducer 来调用**每一个单独的reducer manager**. 然后把他们合并到一个单个的对象里. 从此它不需要再去知道完整的初始状态了. 只需要让子 reducer 来返回一个` undefined` 的初始状态.


```
function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}
```

**注意**

> 这里每一个 reducer 他们手里面的` state` 并不是一样的,他们只负责他们各自对应的 **state片段**.

到这里看上去就不错了. 当随着 app 不断复杂,我们可以把 reducer 分割到不同的文件里,最后在主 reducer 里组合就好. 而且 redux 提供一个工具 -- `combineReducers()` 通过这个可以更加方便的把零散的 reducer 组合起来,所以我们可以重构之前的 todoAPP 如下:

```
import { combineReducers } from 'redux'

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp
```

还有这样的写法:

```
export default function todoApp(state = {}, action) {
  return {
    visibilityFilter: visibilityFilter(state.visibilityFilter, action),
    todos: todos(state.todos, action)
  }
}
```

甚至可以给每一个 reducer 指派不同的`键`, 然后这样用:


```
const reducer = combineReducers({
  a: doSomethingWithA,
  b: processB,
  c: c
})
function reducer(state = {}, action) {
  return {
    a: doSomethingWithA(state.a, action),
    b: processB(state.b, action),
    c: c(state.c, action)
  }
}
```


所有的` combineReducers()` 其实就做一件事情,生成一个函数,然后让每一个 reducer 来通过不同对应的键利用自己的一段 state.最后把结果合并起来赛道一个单个对象.

都能写成这样:

>Note for ES6 Savvy Users

>Because combineReducers expects an object, we can put all top-level reducers into a separate file, export each reducer function, and use import * as reducers to get them as an object with their names as the keys:


```
import { combineReducers } from 'redux'
import * as reducers from './reducers'

const todoApp = combineReducers(reducers)
```

>Because import * is still new syntax, we don't use it anymore in the documentation to avoid confusion, but you may encounter it in some community examples.


最终的代码如下:

```
import { combineReducers } from 'redux'
import { ADD_TODO, TOGGLE_TODO, SET_VISIBILITY_FILTER, VisibilityFilters } from './actions'
const { SHOW_ALL } = VisibilityFilters

function visibilityFilter(state = SHOW_ALL, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return action.filter
    default:
      return state
  }
}

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
          text: action.text,
          completed: false
        }
      ]
    case TOGGLE_TODO:
      return state.map((todo, index) => {
        if (index === action.index) {
          return Object.assign({}, todo, {
            completed: !todo.completed
          })
        }
        return todo
      })
    default:
      return state
  }
}

const todoApp = combineReducers({
  visibilityFilter,
  todos
})

export default todoApp

```

接下来我们看如何创建一个 redux 的 `store`, 它是如何处理` state` 还有如何 在指派一个 action 之后如何调用 reducer.


---

index

learning Redux from todo

- [learning Redux from todo[1]](http://qiita.com/xiangzhuyuan/items/a26e97c6cac476369e37)
- [learning Redux from todo[2]](http://qiita.com/xiangzhuyuan/items/86c5c6508e8481c2dd3a)
- [learning Redux from todo[3]](http://qiita.com/xiangzhuyuan/items/9ce23a8c9f3b26c48838)
- [learning Redux from todo[4]](http://qiita.com/xiangzhuyuan/items/c082e97c3b863602fccf)
- [learning Redux from todo[5]](http://qiita.com/xiangzhuyuan/items/4e6f61a27c068b4f33e7)