React.js で作る"Hello React" からのToDoアプリを作成する~その2: ToDoアプリ完成~
前回の記事で取りあえずは"Hello World!!"が表示された。これをベースにToDo アプリを作っていく。hirahiro56.hatenablog.com
下記のようなアプリイメージのものを開発していく。
【Step.1】index.htmlの作成
まずは、index.htmlを準備する。これはHello World の時と何にも変わらない。
<html> <head> </head> <body> <div id="app-container"></div> <script src="dist/app.js"></script> </body> </html>
【Step.2】TodoListコンポーネントの作成
アプリのルートとなるTodoListコンポーネントを作成する。このコンポーネントは全てのコンポーネントの親玉となるもので、このアプリにおいて状態を監視するべきデータである「残タスク一覧」(実体はオブジェクトのコレクション)を管理する。React.js では状態を管理する必要のあるデータ、つまり、更新されると画面を再描画する必要がるものは全て親玉(ルート)のコンポーネントで管理するように設計する。このような管理するべきデータをstete と呼ぶ。
コードは下記のようになり、app.js として保存する。
var React = require('react'); var Todo = require('./todo'); var InputApp = require('./inputapp'); var generateId = ( function(){ var id = 10; return function(){ return (id++); } } )(); var TodoList = React.createClass({ //todos(ToDoリスト)を初期化 getInitialState() { return { todos: [ {id:1, text:"Sample1"}, {id:2, text:"Sample2"}, {id:3, text:"Sample3"} ] }; }, //引数のidに一致するデータをtodosから削除 deleteTodo(id) { this.setState({ todos: this.state.todos.filter((todo) => { return todo.id !== id; }) }); }, //引数のToDoをToDoリスト(todos)にアペンドする。 updateToDoList:function(todo){ var tmptodos = this.state.todos.concat(todo); this.setState({todos: tmptodos}); }, render() { //ToDoリストのデータをMarkdown形式で書き換える。 var todos = this.state.todos.map((todo) => { return <li key={todo.id}><Todo onDelete={this.deleteTodo} todo={todo} /></li>; }); return ( <div> <InputApp onSave={this.updateToDoList} id={generateId()} /> <ul>{todos}</ul> </div> ); } }); React.render( <TodoList />, document.getElementById('app-container') );
上記コードから、このコンポーネントは、Todo, およびInputAppという子コンポーネントを包括していることが分かると思う。
次のステップで、これらのコンポーネントを実装していくことになる。
【Step.3】InputAppコンポーネントの実装
InputAppコンポーネントの役割は下記2つとなる。
1.フォームの表示
2.フォームの内容(INPUT)を todos に追加する(ToDoリストに追加する)
ソースファイルをinputapp.js とし、下記の通り実装する。
var React = require('react'); var InputApp = React.createClass({ _onKeyDown:function(e){ if(e.keyCode === 13){ var todo = { id: '', text: '' }; todo.text = e.target.value; todo.id = this.props.id; this.props.onSave(todo); //this.props.onSave(e.target.value); e.target.value=""; } }, render: function(){ return ( <div> <h2>Input your ToDo</h2> <input type="text" onKeyDown={this._onKeyDown} /> </div> ); } }); module.exports = InputApp;
【Step.4】ToDoコンポーネントの実装
ToDoコンポーネントの役割としては、下記2つとなる。
1.登録されたToDoの内容を表示する
2.delete ボタン押下時にToDoリスト(todos)から要素を削除する
todo.js として下記の通り実装するが、
削除処理について、実体は親コンポーネント(ToDoList)のdeleteTodoメソッドとなる。つまり、ToDoコンポーネントではToDoリスト(todos)の内容を更新するようなことはせず、親コンポーネントに処理を委譲する(呼び出し & 引数を与える)だけである。React.jsではアプリケーションの状態、所謂state(ToDoリストのtodos[])の管理は親玉(ルート)のコンポーネントに一括で請け負ってもらい、子コンポーネントはstateを持たないように設計することが定石となる。
var React = require('react'); var Todo = React.createClass({ propTypes: { todo: React.PropTypes.shape({ id: React.PropTypes.number.isRequired, text: React.PropTypes.string.isRequired }), // 削除するための処理をI/Fとして定義 onDelete: React.PropTypes.func.isRequired }, // 親に処理を委譲する _onDelete() { this.props.onDelete(this.props.todo.id); }, render() { return ( <div> <span>{this.props.todo.text}</span> <button onClick={this._onDelete}>delete</button> </div> ); } }); module.exports = Todo;
【Step.5】ビルド
最後にbrowserify/reactifyでビルドを実行するが、本ソースはES6, 7のシンタックスを利用しているため、reactifyのharmony オプションをつけてビルドする。
browserify -t [reactify --harmony ] src/app.js -o dist/app.js npm start
上記コマンド実行したら下記URLにアクセスして、ToDoアプリの挙動を確認できる。