LoginSignup
60
64

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-06-14

復習のため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の雛形を作ります。

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

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

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

TodoAppコンポーネントを描画

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

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

TodoAppコンポーネント

renderメソッドを定義

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

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

Todoリストの初期値の定義

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

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

TodoListコンポーネントの定義

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

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><s>{todo.item}</s></li>}
          })
        }
      </ul>
    );
  }
});

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

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

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

TodoCreatorコンポーネントの定義

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

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メソッドを追加します。

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

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

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

TodoCreatorコンポーネントの修正

Addボタンを押したときに、入力した内容がちゃんと消えるようにするために加筆します。

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の表示部分の変更

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}><s>{todo.item}</s></li>
          }
        },this)
      }
    </ul>
  );
}

Todoステータスの更新

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

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

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

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

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

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

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

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

localhost_8000.png

全体コード!

<!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}><s>{todo.item}</s></li>
                  }
                },this)
              }
            </ul>
          );
        }
      });

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

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

60
64
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
60
64