learning Redux from todo[5]


### 使用 React

这里说明的就是其实 Redux 它完成的逻辑是不限于 react 来完成 UI 部分的. 你完全可以使用如`Angular` `Ember` 或者` jQuery` 等 frontend 框架来写. 而只是 Redux 可以更好地和 react 或者 deku 结合使用. 因为从 react 他们的概念定义来说, 描述 UI 的过程就是在说明一个状态的函数. 和 Redux 响应一个动作出发状态更新. 

接下来我们使用 react 来构建这个 todoapp 的 UI 部分.

### 安装 react redux

[react 绑定](https://github.com/reactjs/react-redux) 是单独与 redux 存在,我们现在安装它:

```
npm install --save react-redux
```



> If you don't use npm, you may grab the latest UMD build from unpkg (either a development or a production build). 
> The UMD build exports a global called window.
> ReactRedux if you add it to your page via a \<script\> tag.


### 展示容器组件(用来画 UI, 无逻辑)

**React redux 绑定**封装了`展示和容器组件分离`的概念. [若不熟悉先读它](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0),

很重要,可以离开放下这里,等你.


回来...

我们总结下有什么不同之处:

| \   |展示组件|容器组件|
|:--:|:-----:|:----:|
|目的|看上去像什么|他们是怎么工作的|
|和 Redux通信|不|是|
|怎么读取数据|从属性 props|订阅 redux 的状态|
|怎么修改数据|通过属性里的回调|触发 redux 的动作|
|谁写的|自己用手|通常由react redux 会生成|


我们需要处理的大部分的都是展示组件, 当然也要处理一些容器组件, 他们用来和` reudx store` 连接起来.

技术上来说, 你可以通过调用` store.subscribe()`来写容器组件,但是不建议你那样做.
因为` React redux` 通常会做很多的优化, 他们都是手动无法完成的. 
因为这个, 我们更加趋向于通过使用 react-reudx 的` connect()` 函数来生成他们.

###设计组件体系结构

之前我们同样遇到一个环节就是在[动手之前设计我们的 state 体系](http://redux.js.org/docs/basics/Reducers.html). 现在我们要设计如何组织 UI 结构了. 这里就不是 redux 的范畴了. 可以通过[如何使用 react](https://facebook.github.io/react/docs/thinking-in-react.html) 来学习理解如何处理.

我们在这里的设计很简单了, 我们要显示一个 todo 的 list. 点击 todo 的 item, 它就标记为完成. 而且可以过滤显示没有完成的,或者已经完成的.

---

####展示组件

通过上面大致的讲述,我现在脑海里就是这样一些东西浮现;

- TodoList 用来表示可见的 todo 列表
	- todos:Array 就是一组 todo 的 item, 大概内容就是 {id,text,completed}.
	- onTodoClick(id:number) 是一个回调当一个 todo 的 item 被点击的时候.

- Todo 就是表示一个单独的 todo
	- text:string 当前 todo 的内容
	- completed:boolean 当前是否标记为完成
	- onClick() 是一个回调, 当在 item 被点击的时候触发

- Link 回调用
	- onClick() link 在被点击后触发的回调

- Footer 用来显示什么状态的 toggle
- App 就是当前 app 的根元素

他们完全不依赖redux, 当你把 redux 迁移到其他的框架上时, 这些东西完全不需要修改. 他们只是用来渲染你想要在 UI 上看到的东西

---

#### 容器组件

容器组件在这里充当的角色就是用它来连接展示组件和 redux, 让redux 的数能对应的显示在展示组件上. 比如刚才定义的展示组件` TodoList` 组件它就需要一个容器比如叫` VisibleTodoList`, 然后让它订阅到` Redux store`里的当前对应可见的` todo`, 为了修改可见过滤器,我们需要一个比如` FilterLink` 的容器来完成渲染一个`Link` 来让它完成一个动作的触发当点击它的时候:

- VisibleTodoList 通过当前的显示过滤条件拿到 todo list
- FilterLink 获取当前的过滤条件然后渲染一个 link
	- Filter:string 


####其他组件

有时候很难界定它到底是展示组件还是容器组件. 例如有时候一个表单或者一个函数他们精密的缠绕,如

- AddTodo 就是一个输入框加一个`添加`按钮

技术上讲我们可以把他们分开,但是呢现在这个初级阶段我们最好把它混合在一起. 因为组件的逻辑也不是很麻烦, 随着 app 的成长,如何分割他们就变得越来越明显.


### 动手实现组件

我们先从`展示组件`开始,因为我们不需要考虑如何和 redux 联系起来.

####展示组件

这些都是普通的 React 组件.我们也不会涉及太多,就是写一些无状态的纯函数.

除非我们还需要一些本地的状态或者生命周期的方法.当然也不是说组件必须是函数.只是用函数简单.

如何你之后设计到本地状态,生命周期的方法,或者性能优化的时候可以把它们写成`类`.

 
```components/Todo.js

import React, { PropTypes } from 'react'
// 定义单个 todo 对象
// 需要3个参数,1 click事件, 2 是否完成, 3 显式文本
const Todo = ({ onClick, completed, text }) => (
  <li onClick={onClick} style={{ textDecoration: completed ? 'line-through' : 'none' }} > {text} </li>
)

// 验证3个参数
Todo.propTypes = {
  onClick: PropTypes.func.isRequired,
  completed: PropTypes.bool.isRequired,
  text: PropTypes.string.isRequired
}

export default Todo
```




```components/TodoList.js
import React, { PropTypes } from 'react'
import Todo from './Todo'

const TodoList = ({ todos, onTodoClick }) => (
  <ul>
    {todos.map(todo =>
      <Todo
        key={todo.id}
        {...todo}
        onClick={() => onTodoClick(todo.id)}
      />
    )}
  </ul>
)

TodoList.propTypes = {
  todos: PropTypes.arrayOf(PropTypes.shape({
    id: PropTypes.number.isRequired,
    completed: PropTypes.bool.isRequired,
    text: PropTypes.string.isRequired
  }).isRequired).isRequired,
  onTodoClick: PropTypes.func.isRequired
}

export default TodoList
```



```components/link.js
import React, { PropTypes } from 'react'

const Link = ({ active, children, onClick }) => {
  if (active) {
    return <span>{children}</span>
  }

  return (
    <a href="#"
       onClick={e => {
         e.preventDefault()
         onClick()
      }}
    >
      {children}
    </a>
  )
}

Link.propTypes = {
  active: PropTypes.bool.isRequired,
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired
}

export default Link
```


```components/Footer.js
import React from 'react'
import FilterLink from '../containers/FilterLink'

const Footer = () => (
  <p>
    Show:
    {" "}
    <FilterLink filter="SHOW_ALL">
      All
    </FilterLink>
    {", "}
    <FilterLink filter="SHOW_ACTIVE">
      Active
    </FilterLink>
    {", "}
    <FilterLink filter="SHOW_COMPLETED">
      Completed
    </FilterLink>
  </p>
)

export default Footer
```



```components/App.js
import React from 'react'
import Footer from './Footer'
import AddTodo from '../containers/AddTodo'
import VisibleTodoList from '../containers/VisibleTodoList'

const App = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
    <Footer />
  </div>
)

export default App
```

### 容器组件

现在是时候把这些视图组件 hook 到 redux 里通过创建一些容器组件. 技术上来说, 一个容器组件就是一个 React 的组件( component), 他们通过使用` store.subscribe()`来读取` Redux` 状态树中的部分状态数据,然后给视图组件的` props` 对象,之后展示的过程和逻辑. 你可以手敲一个容器组件, 但是不建议这么做有点傻,建议你通过使用 react redux 的库提供的` connect()` 函数来生成这些容器组件.他们同时提供了很多优化来防止重复没用的渲染.

 为了使用` connect()`,你需要定义一个特殊的函数名字叫做` mapStateToProps`, 它的作用就是告诉当前的 app 如何把 redux 里的特定的状态片段对应到特定组件的` props` 属性上.举个例子, `VisibleTodoList` 需要计算` todos`, 把它传给` TodoList`. 所以我们需要定义一个函数,它能够过滤出` state.todos`, 通过` state.visibilityFilter`, 之后在` mapStateToProps` 里是这样的:
 
```
const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}
```

除了读取 redux 的状态,容器组件还可以分发动作. 通常流行的写法是你可以定义一个` mapDispatchToProps()`函数.他可以接收一个`dispatch()` 方法,然后返回一个回调参数, 这个回调参数就是你想要 hook到视图组件中的. 例如, 我们想要把` VisibleTodoList` 注入一个` onTodoClick` 的属性到` TodoList` 的组件.而且这个` onTodoClick`还可以方法` TOGGLE_TODO` 的动作:

```
const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
    }
  }
}
```

最终,我们通过调用` connect()` 传递2个参数, 创建了` VisibleTodoList`对象.

```
import { connect } from 'react-redux'

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList
```

这些都是 React redux 基本的 API, 但是还有很多方便好用的方法所以[建议你阅读它的文档](https://github.com/reactjs/react-redux),如果你 经常担心` mapStateToProps`创建新的对象,你可以学习[computing derived data](http://redux.js.org/docs/recipes/ComputingDerivedData.html) with [reselect](https://github.com/reactjs/reselect).


接下来是全部的容器组件的代码:


```containers/FilterLink.js

import { connect } from 'react-redux'
import { setVisibilityFilter } from '../actions'
import Link from '../components/Link'

const mapStateToProps = (state, ownProps) => {
  return {
    active: ownProps.filter === state.visibilityFilter
  }
}

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch(setVisibilityFilter(ownProps.filter))
    }
  }
}

const FilterLink = connect(
  mapStateToProps,
  mapDispatchToProps
)(Link)

export default FilterLink
```



```containers/VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case 'SHOW_ALL':
      return todos
    case 'SHOW_COMPLETED':
      return todos.filter(t => t.completed)
    case 'SHOW_ACTIVE':
      return todos.filter(t => !t.completed)
  }
}

const mapStateToProps = (state) => {
  return {
    todos: getVisibleTodos(state.todos, state.visibilityFilter)
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    onTodoClick: (id) => {
      dispatch(toggleTodo(id))
    }
  }
}

const VisibleTodoList = connect(
  mapStateToProps,
  mapDispatchToProps
)(TodoList)

export default VisibleTodoList
```

###其他的组件

 
```containers/AddTodo.js

import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

let AddTodo = ({ dispatch }) => {
  let input

  return (
    <div>
      <form onSubmit={e => {
        e.preventDefault()
        if (!input.value.trim()) {
          return
        }
        dispatch(addTodo(input.value))
        input.value = ''
      }}>
        <input ref={node => {
          input = node
        }} />
        <button type="submit">
          Add Todo
        </button>
      </form>
    </div>
  )
}
AddTodo = connect()(AddTodo)

export default AddTodo
```

###传递 Store

所有的容器组件都能够访问 Redux 的 store, 所以他们可以订阅它. 有个方法就是可以把它当做一个参数 prop 传递给每一个容器组件. 但是这样很无聊,你需要把它在所有视图组件中传递?
就是因为他们在组件树里很深的位置哦.?**不懂**

我们推荐的一个方法就是使用一个特殊的 react redux 组件叫做` <Provider>`,它可以很魔性的让 `store `变得在所有的容器组件中可见, 而不需要再去显示的传递它, 因为你只需要在根组件里引用一次.



```index.js

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

let store = createStore(todoApp)

render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

```

到这里, 一个` todo` app 也就完事儿, 我们回过头来重新查看它的源码, 对比上面我们所讲解过的一切. 再去感受如何 build 一个redux-react app.

-THE END-