React入門(4):ReactでToDoアプリケーションを作成してみた①
モチベーション
Reactに慣れるために簡単なSPAを作成したい
前提条件
Reactの基本文法理解している。
ToDoアプリの構成
以下のようなコンポネントの組み合わせでToDoアプリを構築する。大枠の構成は
- TODO APP - TODO HEADER - TODO CONTAINER - TODO BANNER - TODO LIST ITEM #1 - TODO LIST ITEM #2 ... - TODO LIST ITEM #N - TODO FORM - TODO LIST
のようにする。このReact JSXで表現するとこんな感じになる。
/* [TODO APP] */
var TodoApp = React.createClass({ ... });
/* [TODO BANNER] && [TODO LIST] */
var TodoBanner = React.createClass({ ... });
var TodoList = React.createClass({ ... });
/* [TODO LIST ITEM] */
var TodoListItem = React.createClass({ ... });
/* [TODO FORM] */
var TodoForm = React.createClass({ ... });
React.render(<TodoApp/>, document.body);
ToDoアプリの作成
それでは、各コンポーネントごとに順々に作成して行こう。
App、Headerコンポーネントの作成
まずは全てのコンポーネントの親になるAppコンポーネントとヘッダー部分のHeaderコンポーネントを作成する。特に難しいことはない。コンポーネント作成の基本を守るだけ。注意点としてはカスタムタグを定義する変数名は通常のHTMLタグと区別するために必ず大文字で開始すること。ここではToDoApp
とToDoHeader
がそれにあたる。
"use strict";
var React = require('react');
var ToDoApp = React.createClass({
render: function(){
return(
<ToDoHeader />
);
}
});
var ToDoHeader = <.createClass(
{
render: function(){
return (
<h1>React ToDo</h1>
);
}
}
)
React.render(
<ToDoApp />,
document.body
);
Containerコンポーネントの作成
BannerやFormコンポーネントを集約するContainerコンポーネントを作成する。これも難しいところはない。
var ToDoApp = React.createClass({
render: function(){
return(
<div>
<ToDoHeader />
<ToDoContainter />
</div>
);
}
});
...
var ToDoContainter = React.createClass({
render: function(){
return(
<h1>dummy</h1>
);
}
});
Bannerコンポ−ネントの作成
Bannerコンポーネントを作成する。ここまでは上に同じでReact.createClassの使い方を学ぶだけ。
var ToDoContainter = React.createClass({
render: function(){
return(
<ToDoBanner />
);
}
});
var ToDoBanner = React.createClass({
render: function(){
return(
<h2>ToDo items</h2>
);
}
});
Formコンポーネント
ここから少し覚えることが増えてくる。<input type="text" ref='item' onChange={this.handleChange} value={this.state.item}/>
のonChangeはフォームに文字を入力した時にフォーム内に表示するテキストを変化させるためのイベントハンドラ。onChangeに紐付かせるhandleChange関数ではフォーム内テキストの状態管理を行う。フォーム内のテキストを管理する変数itemの初期状態はnullにしておき、文字を入力するたびにitemを更新する。
<input type="text" ref='item' onChange={this.onChange} value={this.state.item}/>
また、<form onSubmit={this.handleSubmit}>
でAdd itemボタンをクリック時の動作を定義する。ここでは、親コンポーネントToDoContainerが持つitemsにフォーム内テキストの内容を追加し、ToDoリストにデータを追加する。
# Add itemをクリックしたらhandleSubmitが実行される
<form onSubmit={this.handleSubmit}>
また、同じタイミングでフォーム内テキストをitemをnullにすることで、ToDoリストにitem追加後のフィーム内テキストを消す。onChangeやonSubmitのようにReactで使われるイベントリスナーはHTML DOM Event Objectを参考にすれば良い。
# ToDo
handleSubmit: function(e){
e.preventDefault();
this.props.onFormSubmit(this.state.item);
this.setState({item: ''});
React.findDOMNode(this.refs.item).focus();
}
これらを合わせた実装はこのようになる。
var ToDoContainter = React.createClass({
updateItems: function(){
},
render: function(){
return(
<div>
<ToDoBanner />
<ToDoForm onFormSubmit={this.updateItems} />
</div>
);
}
});
...
var ToDoForm = React.createClass({
getInitialState: function(){
return(
{item: ''}
);
},
handleSubmit: function(e){
e.preventDefault();
this.props.onFormSubmit(this.state.item);
this.setState({item: ''});
React.findDOMNode(this.refs.item).focus();
},
handleChange: function(e){
this.setState({
item: e.target.value
});
},
render: function(){
return(
<form onSubmit={this.handleSubmit}>
<input type="text" ref='item' onChange={this.handleChange} value={this.state.item}/>
<input type='submit' value='ADD item' />
</form>
);
}
});
ここまでの状態をブラウザで確認する。
Listコンポーネントの作成
ToDoFormのhandleSubmitをトリガーにフォーム内テキストを管理する配列を更新するupdateItems関数を定義する。<ToDoListItem>文字列</ToDoListItem>
のようにすることでToDoListItemコンポーネントの定義内でthis.props.children
から文字列を取得できる。
var ToDoContainter = React.createClass({
getInitialState: function(){
return (
{items: []}
);
},
updateItems: function(newItem){
var allItems = this.state.items.concat([newItem]);
this.setState({items: allItems});
},
render: function(){
return(
<div>
<ToDoBanner />
<ToDoForm onFormSubmit={this.updateItems} />
<ToDoList items={this.state.items} />
</div>
);
}
});
...
var ToDoList = React.createClass({
render: function(){
var createItem = function(itemText){
return (
<ToDoListItem>{itemText}</ToDoListItem>
);
};
return (
<ul>{this.props.items.map(createItem)}</ul>
);
}
});
var ToDoListItem = React.createClass({
render: function(){
return (
<li>{this.props.children}</li>
);
}
})
これでReactを使ったToDoアプリケーションのベースが完成した。
おまけ:Bootstrapで装飾する
CSSでアレコレいじるのは面倒なので例によってCDN経由でBootstrapを使う。
+++ index.html +++
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>React ToDo</title>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css">
</head>
<body>
<!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script src = "./dist/build/build.js"></script>
</body>
</html>
+++ src/app.jsx +++
"use strict";
var React = require('react');
var TodoApp = React.createClass({
getInitialState: function(){
return {items: []};
},
updateItems: function(newItem){
var allItems = this.state.items.concat([newItem]);
this.setState({items: allItems});
},
render: function(){
return (
<div>
<TodoHeader />
<div className="container theme-showcase">
<div className="jumbotron">
<TodoBanner />
<TodoForm onFormSubmit={this.updateItems} />
<TodoList items={this.state.items} />
</div>
</div>
</div>
);
}
});
var TodoHeader = React.createClass({
render: function(){
return(
<nav className="navbar navbar-inverse ">
<div className="container">
<div className="navbar-header">
<a className="navbar-brand" href="#">React ToDo</a>
</div>
</div>
</nav>
);
}
});
var TodoBanner = React.createClass({
render: function(){
return (
<h2>ToDo List items</h2>
);
}
});
var TodoList= React.createClass({
render: function(){
var createItem = function(itemText){
return (
<TodoListItem> {itemText} </TodoListItem>
);
};
return <ul> {this.props.items.map(createItem)}</ul>;
}
});
//
var TodoListItem = React.createClass({
render: function(){
return(
<li>{this.props.children}</li>
);
}
});
//
var TodoForm = React.createClass({
getInitialState: function(){
return {item: ''};
},
handleSubmit: function(e){
e.preventDefault();
this.props.onFormSubmit(this.state.item);
this.setState({item: ''});
React.findDOMNode(this.refs.item).focus();
return;
},
onChange: function(e){
this.setState({
item: e.target.value
});
},
render: function(){
return (
<div>
<form onSubmit={this.handleSubmit}>
<input className="form-control input-lg" type="text" placeholder="Enter you todo items!!" ref='item' onChange={this.onChange} value={this.state.item}/>
<br/>
<input type='submit' value='Add item' className="btn-lg btn-primary" />
</form>
<br/>
</div>
);
}
});
React.render(
<TodoApp />,
document.body
);
こんな感じのToDoアプリが出来た。
今回は、ここまで。
参考
他の人達がReactで作ったToDoアプリ
- いま最も注目のライブラリ「React.js」でシングルページアプリケーションを作ってみよう! 【前編】
- React • TodoMVC
- Building a Todo app with React.js
React eventハンドリング
JS
- HTML DOM Event Object
- JavaScript - Arrayの基礎知識と各メソッドの使用方法 - Qiita
- 特定の入力欄にフォーカスを合わせるJavaScriptのサンプル。スクリプトのソースと使用例を紹介。
- Array.prototype.concat() - JavaScript | MDN
- イベントハンドラの this と event.target, +α