ステップバイステップReact.jsで作るTodoアプリ


復習のためReact.jsでTodoアプリのViewを書いてみました。

このTodoアプリを通して次のことを勉強出来ました。

- stateとpropsの使い方
- bindを使ってコールバック関数内のオブジェクト指定

まだコンポーネント分割の単位をしっかりと見通せてません。

では、書いていきます。

## ファイル構造

```
MyApp
├── index.html
└── react-0.13.3
    ├── README.md
    ├── build
    │   ├── JSXTransformer.js
    │   ├── react-with-addons.js
    │   ├── react-with-addons.min.js
    │   ├── react.js
    │   └── react.min.js
```

## HTML雛形作成
React.jsでReactコンポーネントを作る前にHTMLの雛形を作ります。

```html:index.html
<!--index.html-->
<!DOCTYPE html>
<html>
  <head>
    <script src="./react-0.13.3/build/react.js"></script>
    <script src="./react-0.13.3/build/JSXTransformer.js"></script>
  </head>
  <body>
    <div id="myApp"></div>
    
    <script type="text/jsx">
      <!-- 今回はここにReactのコードを書く -->
    </script>
    
  </body>
</html

```

divタグ内のmyAppにReactで書いたプログラムをロードします。

myAppにロードするReactのコードは```<script type="text/jsx">```と```</script>```の間に書く。

##必要なコンポーネント
コードを書き始める前に必要なコンポーネントを考えます。

![react-todo-components.png](https://qiita-image-store.s3.amazonaws.com/0/19083/82e788e1-602a-7579-f9d7-c38c3000cad1.png "react-todo-components.png")


今回は3つのReactコンポーネントを作ります。

- TodoApp　・・・アプリ全体
- TodoCreator　・・・Todoを新規で追加するため
- TodoList　・・・　Todoリストを描画するため

##TodoAppコンポーネントを描画

``<script type="text/jsx">``と``</script>``タグの間の一番最後でrenderメソッドで描画するコンポーネントとロードするdivのidを指定します。

```jsx
React.render(
  <TodoApp/>,
  document.getElementById('myApp')
);
```

##TodoAppコンポーネント

###renderメソッドを定義

```jsx
var TodoApp = React.createClass({
  render: function(){
    return (
      <div className="todoApp">
        <TodoCreator/>
        <TodoList/>
      </div>
    );
  }
});
```

TodoCreatorコンポーネントとTodoListコンポーネントを返すTodoAppコンポーネントを定義しています。

###Todoリストの初期値の定義

Todoリストの初期値をTodoAppクラス内で定義します。
テストの為に、sampleというキーと0(未完了/false)の値をセットしています。(後でこの中身を消して空のJSONオブジェクトとして定義します。)

```jsx
getInitialState: function() {
  return {
    todos = [{item:"sample", status:0}
  }
},
```


###TodoListコンポーネントの定義
Todoリストを表示するためのTodoListコンポーネントを定義します。

```jsx
var TodoList = React.createClass({
  render: function() {
    return (
      <ul>
        {
          this.props.todos.map(function(todo, i){
            if (todo.status == 0) {return <li key={i}>{todo.item}</li>}
            else {return <li>
{todo.item}
</li>}
          })
        }
      </ul>
    );
  }
});
```

次にTodoAppコンポーネントからgetInitialStateで定義したtodosのステートを渡すために``<TodoList/>``を次のように書き換えます。

```
// <TodoList /> // 変更前
<TodoList todos={this.state.todos} />
```

これでtodosの状態をTodoListコンポーネントに渡し、TodoList側でpropsを通じてtodosの中身を参照することになります。


###TodoCreatorコンポーネントの定義
Todoの内容を入力し、Addボタンを押してtodosリストに追加するためのコンポーネントを作ります。

```jsx
var TodoCreator = React.createClass({

  _onAdd: function(){
    var newTodo = this.refs.inputText.getDOMNode().value;
      this.props.onAdd(newTodo);
  },

  render: function(){
    return (
      <div className="TodoCreator">
        <input type="text" ref="inputText" placeholder="Input your new to\
do" />
        <button onClick={this._onAdd}>Add</button>
      </div>
    );
  }

 });

```

Addボタンが押されたら_onAddメソッドを呼び出します。次に_onAddメソッド内ではgetDOMNodeを使ってtextボックス内の値を取得して、newTodo変数に格納し、propsを通じてnewTodoを引数に親であるTodoAppコンポーネントのonAddメソッドを実行します。

####TodoAppのonAddメソッド
TodoAppコンポーネントにonAddメソッドを追加します。

```jsx
onAdd: function(newTodo){
  this.setState({
    todos : this.state.todos.concat({item:newTodo, status:0})
  });
},
```

onAddメソッドの内部でsetStateを実行todosの状態を更新します。

こうすることで、todosのステートが更新され、TodoListの表示が更新されます。

##TodoCreatorコンポーネントの修正
Addボタンを押したときに、入力した内容がちゃんと消えるようにするために加筆します。

```jsx
var TodoCreator = React.createClass({

  getInitialState: function(){
    return {
      value: ""
    }
  },

  _onAdd: function(){
    var newTodo = this.refs.inputText.getDOMNode().value;
    this.props.onAdd(newTodo);
    this.setState({value: ""});
  },

  _onChange: function(e){
    this.setState({
      value: e.target.value
    });
  },

  render: function(){
    return (
      <div className="TodoCreator">
        <input type="text" value={this.state.value} ref="inputText" place\
holder="Input your new todo" onChange={this._onChange}/>
        <button onClick={this._onAdd}>Add</button>
      </div>
    );
  }
});
```

valueという変数をgetInitialStateで定義しておき、inputボックスの中身が変化したら_onChangeメソッドでvalue変数を更新します。Addボタンが押されたら、親コンポーネントのtodosに更新作業を行った後に、value変数の中身をNullに戻しています。

##完了Todoの更新
Todoの完了を示すチェックボックを表示し、完了したものは取り消し線で表示するようにTodoListコンポーネントを修正します。

###Todoの表示部分の変更
```jsx
render: function() {
  return (
    <ul>
      {
        this.props.todos.map(function(todo,i){
          if (todo.status == 0) { 
            return (
               <li key={i}>
                 <input type="checkbox" 
                   onClick={this._onDelete.bind(this,i )}/>
                   {todo.item}
                </li>
            )
          } else {
            return <li key={i}>
{todo.item}
</li>
          }
        },this)
      }
    </ul>
  );
}
```

###Todoステータスの更新
チェックボックスがクリックされると、まずTodoListコンポーネントの_onDeleteが呼ばれ_onDelete内の```this.props.onDelete```で親であるTodoAppコンポーネントのonDeleteメソッドが実行されています

まず、TodoListコンポーネントの_onDeleteの定義から

```jsx
_onDelete: function(i){
  this.props.onDelete(i);
}
```

次にTodoAppコンポーネントのonDeleteメソッドを定義します。

```jsx
onDelete: function(i){
  var targetTodo = this.state.todos[i];
  targetTodo.status = 1;
  this.setState({
    todos: this.state.todos
  });
},
```

また、propsで渡すためにTodoAppコンポーネントからTodoListへonDeleteを渡すためにTodoListのロード部分を変更します。


```jsx
<TodoList todos={this.state.todos} onDelete={this.onDelete}/>
```

これでindex.htmlファイルをブラウザで表示すると下図のTodo画面が出来上がります。

![localhost_8000.png](https://qiita-image-store.s3.amazonaws.com/0/19083/f0b6ddd0-3531-50b1-0027-d0018ceaf45d.png "localhost_8000.png")



##全体コード!

```html
<!DOCTYPE html>
<html>
  <head>
    <script src="./react-0.13.3/build/react.js"></script>
    <script src="./react-0.13.3/build/JSXTransformer.js"></script>
  </head>
  <body>
    <div id="myApp"></div>
    <script type="text/jsx">

      var TodoApp = React.createClass({

        getInitialState: function(){
          return {
            todos: []
          }
        },

        onAdd: function(newTodo){
          this.setState({
            todos : this.state.todos.concat({item:newTodo, status:0})
          });
        },

        onDelete: function(i){
          var targetTodo = this.state.todos[i];
          targetTodo.status = 1;
          this.setState({
            todos: this.state.todos
          });
        },


        render: function(){
          return (
            <div className="TodoApp">
              <TodoCreator onAdd={this.onAdd}/>
              <TodoList todos={this.state.todos} onDelete={this.onDelete}/>
            </div>
          );
        }
        });

      var TodoCreator = React.createClass({

        getInitialState: function(){
          return {
            value: ""
          }
        },

        _onAdd: function(){
          var newTodo = this.refs.inputText.getDOMNode().value;
          this.props.onAdd(newTodo);
          this.setState({value: ""});
        },

        _onChange: function(e){
          this.setState({
            value: e.target.value
          });
        },


        render: function(){
          return (
            <div className="TodoCreator">
              <input type="text" value={this.state.value} ref="inputText" placeholder="Input your new todo" onChange={this._onChange}/>
              <button onClick={this._onAdd}>Add</button>
            </div>
          );
        }
      });

      var TodoList = React.createClass({

        _onDelete: function(i){
          this.props.onDelete(i);
        },

        render: function() {
          return (
            <ul>
              {
                this.props.todos.map(function(todo,i){
                  if (todo.status == 0) { 
                    return (
                      <li key={i}>
                        <input type="checkbox"
                               onClick={this._onDelete.bind(this,i )}/>{todo.item}
                      </li>
                    )
                  } else {
                    return <li key={i}>
{todo.item}
</li>
                  }
                },this)
              }
            </ul>
          );
        }
      });

      React.render(
        <TodoApp/>,
        document.getElementById('myApp')
      );

    </script>
  </body>
</html>

```