モチベーション
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(, 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, +α