Redux ExampleのTodo Listをはじめからていねいに(1)

[Reduxの公式ExampleにあるTodo List](http://redux.js.org/docs/basics/ExampleTodoList.html)を少しづつ書いてみました。

最終的な動作イメージは以下のような感じです。
[Github Pages](http://xkumiyu.github.io/redux-todos/)にもあげていますので、触ってみてください。

![redux-todos.gif](https://qiita-image-store.s3.amazonaws.com/0/35980/86ea2c70-56de-3e03-aea0-4cff4e490fb2.gif)

基本的なReduxの説明や環境構築などは他のドキュメントに譲るとして、
ここでは各機能ごとに動くものを作りながら、最終的にExampleの形になることを目指します。
Reactがだいたい分かってて（[チュートリアル](https://facebook.github.io/react/docs/tutorial-ja-JP.html)をやったくらい）で、
Reduxを触り始めたくらいの人を対象としています。

Reduxを触り始めて日が浅いので、間違いもあるかと思います。ぜひご指摘ください。

ExampleのTodo Listの機能は次の3つです。

1. TodoをTodo Listに追加する「Add Todo」
2. Todoの完了・未完了を切り替える「Toggle Todo」
3. 表示するTodo Listを完了または未完了のTodoだけにする「Filter Todo」

また、動かした環境は次の通りです。

* react: 0.14.7
* redux: 3.2.1
* babel: 6.5.2

今回は、「Add Todo」の機能を実装します。
~~最初なのでボリュームが多くなったので、「Add Todo」実装は2回に分けます。~~
1回にまとめました。

# 1. Hello World

何はともあれHello Worldを表示させます。

react-reduxのProviderがreactのコンポーネントをラップしており、
これが最上位になります。

``` index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import App from './components/App'

render(
  <Provider>
    <App />
  </Provider>,
  document.getElementById('root')
)

```

Appコンポーネントでは、Hello Worldを表示させているだけです。

``` components/App.js
import React from 'react'

const App = () => (
  <div>
    Hello World!
  </div>
)

export default App
```

ここまででとりあえず、動きます。
Providerはstoreを呼ぶ必要があるので、Warningが出ます。

```
Warning: Failed propType: Required prop `store` was not specified in `Provider`.
Warning: Failed Context Types: Required child context `store` was not specified in `Provider`.
```

# 2. actionCreatorで発行したactionをreducerに渡してstoreのstateを更新する

## Actions

addTodoというactionCreatorを作ります。
actionCreatorは純粋な関数であり、actionを発行するだけです。

actionはactionTypeとデータで構成されるオブジェクトです。
ここでは、ADD_TODOがactionTypeで、データはidとtextです。
（todoは複数なので、idは後々、必要になります。）
データは、配列でもオブジェクトでも、関数でもなんでもいいです。

actionCreatorによって作られたactionはdispatchに渡されます。

``` actions/index.js
let nextTodoId = 0
export const addTodo = (text) => {
  return {
    type: 'ADD_TODO',
    id: nextTodoId++,
    text
  }
}
```

## Reducers

reducerも単なる関数で、現在のstateとactionを受け取り、新しいstateを返します。
todoというreducerは、actionTypeがADD_TODOのとき、{ id: action.id, text: action.text }という新しいstateを返します。

``` reducers/index.js
const todo = (state, action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        id: action.id,
        text: action.text
      }
    default:
      return state
  }
}
export default todo
```

## Store

storeはアプリケーションで単一のもので、stateを保持します。
createStore関数でreducerを呼び出すことで作られます。
また、Providerにはstoreを渡します。これで、Warningは消えるはずです。

``` index.js
import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todo from './reducers'
import App from './components/App'

let store = createStore(todo)

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

ここまでで、action -> reducer -> storeの一連の流れができました。
実際にstateが更新されるのを見てみます。

1. actionCreatorであるaddTodo('Hello World!')で{ id: 0, text: 'Hello World' }というactionが作成される。
2. store.dispatch関数にactionを渡すことで、todo reducerにaction = { id: 0, text: 'Hello World' }, state = 現在のstate(初期値を与えてないのでundefinedだと思います。)
3. todo reducerでは、新しいstate = { id: 0, text: 'Hello World' }を返します。

storeが保持しているstateはstore.getStateで取得できます。

``` index.js
import { createStore } from 'redux';
import todo from './reducers';
import { addTodo } from './actions'

let store = createStore(todo)

store.dispatch(addTodo('Hello World!'))
console.log(store.getState()) // => Object {id: 0, text: "Hello World!"}
```

# 3. storeで保持したstateをViewで表示する

## Todo Listの作成

viewで表示する前に、少しだけreducerを書き換えます。
stateでtodoを保持することはできましたが、このままでは1つのtodoしか
保持できないので、複数のtodoを保持できるよう拡張します。

storeの生成もcreateStore(todos)とすることで、todos関数が呼び出され、
その中でtodo関数が呼び出されます。

``` reducers/todos.js
const todo = (state, action) => {
  ...
}

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        todo(undefined, action)
      ]
    default:
      return state
  }
}

export default todos
```

また、今後のことを考えtodosのreducersを別ファイル（index.jsをtodos.js）にしています。
exampleではtodos以外のreducerを使うからであって、1つしか使わない場合は必要ないです。

combineReducersでは、その名の通り複数のreducersを結合することができます。
createStore(todos)では、state = [todo1, todo2]となりますが、
結合されたtodoAppをつかったcreateStore(todoApp)では、
state = {todos = [todo1, todo2]}といった形で保持されます。

``` reducers/index.js
import { combineReducers } from 'redux'
import todos from './todos'

const todoApp = combineReducers({ todos })
export default todoApp
```

## ContainerとComponent

viewはreactのコンポーネントで実装されますが、大きく2種類あります。
[公式サイト](http://redux.js.org/docs/basics/UsageWithReact.html)によると、

* Presentational Components
  * Reactでいう普通のコンポーネント。
  * Reduxの要素はないので、react-reduxはimportしない。
  * データは上位のコンポーネントからpropsとして受け取る。
  * データの変更もpropsからコールバックさせて行う。
* Container Components
  * Redux特有のもので、react-reduxをimportする。
  * データは、storeのstateから渡される。
  * データの変更もactionをdispatchに渡すことで行われる。
  * connect()を使ってコンポーネントをラップすることで、dispatchやstateが使えるようになる。


## ToDoコンポーネントとToDoListコンポーネントを作る

Todoコンポーネントは、propとして渡されてきたtextを表示するだけです。

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

const Todo = ({ text }) => (
  <li>
    {text}
  </li>
)

Todo.propTypes = {
  text: PropTypes.string.isRequired
}

export default Todo
```

TodoListコンポーネントは、propとして渡されてきたtodosの各要素をTodoコンポーネントに
渡します。ここで、配列としてコンポーネントを複数生成するときkeyが必要になります。
{...todo}はtodoのすべての要素です。id={todo.id} text={todo.text}と同じです。

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

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

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

export default TodoList
```

## コンポーネントをconnectするコンテナ

VisibleTodoListコンテナはTodoListコンポーネントをconnectしています。
mapStateToPropsでは、storeに格納してあるstateをpropsとして使えるようにしています。
実際には、stateすべてではなく、state.todosがtodosとして渡すようになっています。

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

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

const VisibleTodoList = connect(
  mapStateToProps
)(TodoList)
export default VisibleTodoList

```

## ようやくブラウザに表示

Hello Worldを表示させているだけのAppコンポーネントに
（TodoListコンポーネントをconnectした）VisibleTodoListコンテナを表示させます。

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

const App = () => (
  <div>
    <VisibleTodoList />
  </div>
)
export default App
```

まだTodoを追加するフォームはないので、手動でtodoを追加させます。
index.jsからdispatch関数に渡すことでstateを更新します。

``` index.js
let store = createStore(todo)

store.dispatch(addTodo('Hello React!'))
store.dispatch(addTodo('Hello Redux!'))
```

![スクリーンショット 2016-03-08 21.17.33.png](https://qiita-image-store.s3.amazonaws.com/0/35980/ef07c34f-0123-1dff-c518-ac6b7cee0530.png)

デバッグは[React Developer Tools](https://facebook.github.io/react/blog/2015/09/02/new-react-developer-tools.html)が便利です。（画面右にあるやつです。）

# 4. フォームからtodoを追加

フォームからtodoを追加するために、AddTodoコンポーネントを作ります。

[公式ドキュメント](http://redux.js.org/docs/basics/UsageWithReact.html)では、コンポーネントでもなくコンテナでもなく、
Other Componentsってなってます。
ほんとはコンポーネントとコンテナに分けてもいいんだけど、
小さすぎる場合は一緒にしてもいいんじゃない？ってことだと思います。

buttonをクリックすると、`dispatch(addTodo(input.value))`で
inputに入ってる値をtodoに追加します。
input変数は先に宣言しておき、ref以下の関数でinput変数にinput要素を格納しています。

[Reactの公式サイトReference](https://facebook.github.io/react/docs/more-about-refs.html)には、refのコールバック属性の使い方が書かれており、
コンポーネントがマウントした直後に実行され、コンポーネント自身が
変数として格納されます。
ここでは、node変数であり、それをinput変数に格納しています。

``` containers/AddTodo.js
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'

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

  return (
    <div>
      <input ref={(node) => {
        input = node
      }} />
      <button onClick={() => {
        dispatch(addTodo(input.value))
        input.value = ''
      }}>
        Add Todo
      </button>
    </div>
  )
}
AddTodo = connect()(AddTodo)

export default AddTodo
```

AppコンポーネントにAddTodoコンポーネントを追加します。

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

const App = () => (
  <div>
    <AddTodo />
    <VisibleTodoList />
  </div>
)
export default App
```

これで、「Add Todo」機能の完成です。
ここまでのソースコードは[GitHub](https://github.com/xkumiyu/redux-todos/tree/add-todo)にあげています。

続きます。。
次回、Todoの完了・未完了を切り替える「Toggle Todo」機能を実装します。

# 関連記事

* [Redux ExampleのTodo Listをはじめからていねいに(1)](http://qiita.com/xkumiyu/items/9dfe51d2bcb3bdb06da3) -> この記事
* [Redux ExampleのTodo Listをはじめからていねいに(2)](http://qiita.com/xkumiyu/items/e7e1e8ed6a5d6a6e20dd)
* [Redux ExampleのTodo Listをはじめからていねいに(3)](http://qiita.com/xkumiyu/items/1ba476b8043b71561f52)

# 参考したサイト

* http://redux.js.org/index.html
* http://fukajun.org/66
* http://blog.bokuweb.me/entry/redux-tutorial