モチベーション

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タグと区別するために必ず大文字で開始すること。ここではToDoAppToDoHeaderがそれにあたる。

"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>
    );
  }
});

ここまでの状態をブラウザで確認する。

20150531_react_todo__part1_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アプリケーションのベースが完成した。

20150531_react_todo_part1_list_part

おまけ: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アプリが出来た。

20150531_react_todo_part1

今回は、ここまで。

参考

他の人達がReactで作ったToDoアプリ

React eventハンドリング

JS

Bootstrap