ES6版React.jsチュートリアル

# はじめに
* 初心者による初心者のための記事
* React.js公式チュートリアルの和訳風
* ES6

さらっと仮想DOM調べてみたよ、ぐらいの人向けです。
JavaScript初心者、ES6初心者、React初心者向けかつ僕がそうです。
なので間違いがあるかもしれません。見つけたら教えて下さい。

公式のチュートリアルはこちらです。

* [React Tutorial](http://facebook.github.io/react/docs/tutorial.html)
* [React チュートリアル (和訳)](http://facebook.github.io/react/docs/tutorial-ja-JP.html)
* [公式Documentのソース (Markdown)](https://github.com/facebook/react/tree/master/docs/docs)

## 環境
* Mac OSX
* Node.js v0.12.7
* Gulp 3.9.0 
* React 0.13.3

# 作るもの
公式の[Tutorial](
http://facebook.github.io/react/docs/tutorial.html)に沿って、シンプルなコメントボックスをReactで作成します。

次の機能を提供するものです。

* すべてのコメントの表示機能
* コメント投稿フォーム
* バックエンドサーバーとの連携

また、次の特徴を持っています。

* 更新最適化: 新しいコメントはサーバーに保存される前にリストに表示して体感速度を速くします
* ライブアップデート: 他の人が投稿したコメントもリアルタイムで更新されます
* Markdownサポート: マークダウンが使えます

# 準備
## nodeインストール
npmを使うため、なにはともあれnodeをインストールします。npmはnode用のパッケージ管理システムです。yumとかgemとかbrewとかのnode版ですね。

nodeのインストールはndenvを使います。ndenvのインストールにはanyenvを使います。anyenvをインストールしてない方はインストールしてください。

参考: [Mac OS Xに**env管理ツールのanyenvをインストール](http://qiita.com/nownabe/items/72096340ed59e3b3e348)

まずはndenvをインストールします。

```bash
anyenv install ndenv
exec $SHELL -l
```

次はndenvを使ってnodeをインストールします。

```bash
ndenv install v0.12.7
ndenv global v0.12.7
```

`ndenv install --list`でインストール可能なバージョン一覧を確認できます。

## gulpインストール
gulpは[ビルドツール](http://qiita.com/cognitom/items/db88babebeb869a4a198)です。今イケてるらしいです。GuardとかGruntとかの仲間ですね。

npmでサクッとインストールします。`-g`はグローバルにインストールするというオプションです。

```bash
npm install -g gulp
ndenv rehash
```

## ディレクトリ作成
チュートリアルで使用するディレクトリを作成して入っときます。

```bash
mkdir react_es6
cd react_es6
```

## package.json作成
チュートリアルで使用するJavascriptのパッケージを管理するため`package.json`を作成します。

`npm init`コマンドで`package.json`できます。
色々聞かれるので適当に答えていきます。

```bash
$ npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg> --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (react_es6)
version: (1.0.0)
description: React Tutorial
entry point: (index.js)
test command:
git repository:
keywords:
author: nownabe
license: (ISC) MIT
About to write to /Users/nownabe/study/tutorials/react_es6/package.json:

{
  "name": "react_es6",
  "version": "1.0.0",
  "description": "React Tutorial",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "nownabe",
  "license": "MIT"
}


Is this ok? (yes)
```

## パッケージ追加
`package.json`のひな形ができたので、開発に必要なパッケージを追加していきます。
パッケージは`npm install パッケージ名 --save-dev`コマンドで追加します。

```bash
npm install --save-dev babelify@6.4.0 
npm install --save-dev body-parser
npm install --save-dev browser-sync
npm install --save-dev browserify
npm install --save-dev express
npm install --save-dev jquery
npm install --save-dev marked
npm install --save-dev node-dev@2.6.2
npm install --save-dev react
npm install --save-dev vinyl-buffer
npm install --save-dev vinyl-source-stream
```

以上がチュートリアルで使うパッケージです。

最終的に`package.json`はこのようになります。
バージョンは異なるかもしれません。

```json:package.json
{
  "name": "react_es6",
  "version": "1.0.0",
  "description": "React Tutorial",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "nownabe",
  "license": "MIT",
  "devDependencies": {
    "babelify": "^6.1.3",
    "body-parser": "^1.13.3",
    "browser-sync": "^2.8.2",
    "browserify": "^11.0.1",
    "express": "^4.13.3",
    "jquery": "^2.1.4",
    "marked": "^0.3.5",
    "node-dev": "^2.6.2",
    "react": "^0.13.3",
    "vinyl-buffer": "^1.0.0",
    "vinyl-source-stream": "^1.1.0"
  }
}
```

## サーバー準備
このチュートリアルでは最終的にサーバーに対してJSONを保存・取得します。そのためJSONを処理するだけのサーバーを用意します。

まずは空のJSONを用意しておきます。

```bash
touch comments.json
```

次にサーバーアプリケーション`server.js`を作成します。

```js:server.js
var fs = require('fs');
var path = require('path');
var express = require('express');
var bodyParser = require('body-parser');
var app = express();

app.set('port', (process.env.PORT || 3000));

app.use('/', express.static(__dirname));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));

app.get('/comments.json', function(req, res) {
  fs.readFile('comments.json', function(err, data) {
    res.setHeader('Cache-Control', 'no-cache');
    res.json(JSON.parse(data));
  });
});

app.post('/comments.json', function(req, res) {
  fs.readFile('comments.json', function(err, data) {
    var comments = JSON.parse(data);
    comments.push(req.body);
    fs.writeFile('comments.json', JSON.stringify(comments, null, 4), function(err) {
      res.setHeader('Cache-Control', 'no-cache');
      res.json(comments);
    });
  });
});


app.listen(app.get('port'), function() {
  console.log('Server started: http://localhost:' + app.get('port') + '/');
});
```

## gulpfile.js
Gulpを使ってソースコードに更新があったら自動でビルドしてブラウザに反映させるようにします。

Gulpの設定は`gulpfile.js`に書きます。

```javascript:gulpfile.js
var babelify = require('babelify');
var browserify = require('browserify');
var browserSync = require('browser-sync');
var buffer = require('vinyl-buffer');
var gulp = require('gulp');
var node = require('node-dev');
var source = require('vinyl-source-stream');

function errorHandler(err) {
  console.log('Error: ' + err.message);
}

// 自動ブラウザリロード
gulp.task('browser-sync', function() {
  browserSync({
    proxy: {
      target: 'http://localhost:3000'
    },
    port: 8080
  });
});

// Javascriptへのビルド
// ES6かつJSXなファイル群をbuild/bundle.jsへ変換する
gulp.task('build', function() {
  browserify({entries: ['./index.js']})
    .transform(babelify)
    .bundle()
    .on('error', errorHandler)
    .pipe(source('bundle.js'))
    .pipe(buffer())
    .pipe(gulp.dest('./build'))
    .pipe(browserSync.reload({stream: true}));
});

// ローカルサーバーの起動
gulp.task('server', function() {
  node(['./server.js']);
});

// ファイル監視
// ファイルに更新があったらビルドしてブラウザをリロードする
gulp.task('watch', function() {
  gulp.watch('./index.js', ['build']);
  gulp.watch('./index.html', ['build']);
  gulp.watch('./components/*.js', ['build']);
});

// gulpコマンドで起動したときのデフォルトタスク
gulp.task('default', ['server', 'build', 'watch', 'browser-sync']);
```

Gulpの詳しいことは次の記事などを参照してください。

* [Gulp.js入門 – コーディングを10倍速くする環境を作る方法まとめ](http://liginc.co.jp/web/tutorial/117900)
* [サックっとbrowser-syncを設定](http://qiita.com/itoz/items/2bd246606c69c33684e8)
* [React.js + Babel + Browserify + gulp の環境を作ってみた](http://qiita.com/hkusu/items/e068bba0ae036b447754)

以上で環境の準備は完了です。

# Getting Started
## index.htmlを作る
まずはブラウザに表示される`index.html`を作成しましょう。

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>React Tutorial ES6</title>
  </head>
  <body>
    <header>
      <h1>React Tutorial ES6</h1>
    </header>
    <div id="container"></div>
    <script src="build/bundle.js"></script>
  </body>
</html>
```

これは内容がなにもない空HTMLですが、`div#container`にReactで生成された仮想DOMが適用されていくことになります。

## 最初のコンポーネントを作る
Reactではすべてが階層的なコンポーネントから構成されます。これから作るコメントボックスは次のようなコンポーネントの構造を持っています。

```
- CommentBox
  - CommentList
    - Comment
  - CommentForm
```

まずはCommentBoxコンポーネントを作ってみましょう。
次のような`index.js`を作成してください。

```js:index.js
import React from 'react'

class CommentBox extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return(
      <div className='commentBox'>
        Hello, world! I am a CommentBox.
      </div>
    );
  }
}

React.render(
  <CommentBox />,
  document.getElementById('container')
);
```

`index.js`ができたら次のコマンドでgulpを起動してください。

```bash
gulp
```

ブラウザが起動されて"Hello, world! I am a CommentBox."という文が表示されたと思います。以降はファイルを編集して保存すれば自動でJSX/ES6がJavascriptに変換され、ブラウザもリロードされます。新しくファイルを作った時はgulpの再起動が必要です。`Ctrl + C`で終了して同じコマンドで起動してください。

`index.js`の中身ですが、1行目でReact本体をインポートしています。Reactのコンポーネントは`React.Component`を継承したクラスとして定義します。今回は`CommentBox`というコンポーネントを作成しています。

`React.render()`メソッドがReactを開始するメソッドです。また、ここで指定するコンポーネントがReactのルート（根）コンポーネントになります。ここでは`CommentBox`コンポーネントをルートにしています。

`CommentBox`クラスにも`render()`メソッドがあり、Reactのコンポーネントを返します。ここではReactの`div`コンポーネントを返しています。`render()`メソッドが返すのはあくまでも実際のDOMノードではなくReactのコンポーネントです。

Reactではこのようにルートコンポーネントからツリー状にコンポーネントをレンダリングしていきます。

`constructor(props)`はいわゆるコンストラクタです。ES6のクラスベースでReactコンポーネントを作るときはこのメソッドでコンポーネントを初期化します。明示的にコンストラクタを定義しない場合はスーパークラスのコンストラクタが呼ばれるため、中身が`super(props)`だけのときは省略することもできます。`CommentBox`のコンストラクタには後で初期化処理を追加します。

### JSX
この`index.js`はJSXという形式で書かれています。JSXはJavascriptの中にHTMLタグのような形でReactコンポーネントを直接書ける形式です。JSXは最終的にはbabelifyでJavascriptに変換されます。

JSXを使わずに直接Javascriptで書くこともできます。例えば次の2つは同じです。


```js
// JSX
React.render(
  <CommentBox />,
  document.getElementById('container')
);

// Javascript
React.render(
  React.createElement(CommentBox, null),
  document.getElementById('container')
);
```

## コンポーネントを組み合わせる
続いて`CommentList`コンポーネントと`CommentForm`コンポーネントを作成し、コンポーネントを組み合わせます。

`index.js`を次のように編集してください。

```js:index.js
import React from 'react'

class CommentBox extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return(
      <div className='commentBox'>
        <h2>Comments</h2>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
}

class CommentList extends React.Component {
  render() {
    return(
      <div className='commentList'>
        Hello, world! I am a CommentList.
      </div>
    );
  }
}

class CommentForm extends React.Component {
  render() {
    return(
      <div className='commentForm'>
        Hello, world! I am a CommentForm.
      </div>
    );
  }
}

React.render(
  <CommentBox />,
  document.getElementById('container')
);
```

少し長くなりましたがやっていることは単純です。`CommentList`と`CommentForm`という新しいコンポーネントを作成し、`CommentBox`コンポーネントの子コンポーネントとしてレンダリングしているだけです。

Reactではこのように階層的にコンポーネントを組み合わせていきます。

## ファイルを分割する
コンポーネントが増えて`index.js`の見通しが悪くなってきたのでコンポーネントごとにファイルをわけます。

まずはコンポーネントファイルをまとめる`components`ディレクトリを作成します。

```bash
mkdir components
```

次にこれまで作成したコンポーネントをそれぞれひとつのファイルにします。

```js:components/CommentList.js
import React from 'react'

export default class CommentList extends React.Component {
  render() {
    return(
      <div className='commentList'>
        Hello, world! I am a CommentList.
      </div>
    );
  }
}
```

```js:components/CommentForm.js
import React from 'react'

export default class CommentForm extends React.Component {
  render() {
    return(
      <div className='commentForm'>
        Hello, world! I am a CommentForm.
      </div>
    );
  }
}
```

```js:components/CommentBox.js
import React from 'react'
import CommentList from './CommentList'
import CommentForm from './CommentForm'

export default class CommentBox extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    return(
      <div className='commentBox'>
        <h2>Comments</h2>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
}
```

外のファイルから使えるように`export default`をつけるのを忘れないで下さい。最後に`index.js`は次のようになります。

```js:index.js
import React from 'react'
import CommentBox from './components/CommentBox'

React.render(
  <CommentBox />,
  document.getElementById('container')
);
```

だいぶすっきりしました。

## プロパティを使う
親コンポーネントは子コンポーネントに値を渡すことができます。Reactではプロパティと呼ばれています。

プロパティを使った`Comment`コンポーネントを新しく作成します。

```js:components/Comment.js
import React from 'react'

export default class Comment extends React.Component {
  render() {
    return(
      <div className='comment'>
        <h3 className='commentAuthor'>
          {this.props.author}
        </h3>
        {this.props.children}
      </div>
    );
  }
}
```

親から渡されたプロパティは`this.props`で参照できます。
また、JSXに変数の値を埋め込むときは`{変数名}`を使います。

次にプロパティを渡す側の親コンポーネントを編集します。`Comment`の親は`CommentList`です。

```js:components/CommentList.js
import React from 'react'
import Comment from './Comment'

export default class CommentList extends React.Component {
  render() {
    return(
      <div className='commentList'>
        <Comment author='Pete Hunt'>This is one comment</Comment>
        <Comment author='Jordan Walke'>This is *another* comment</Comment>
      </div>
    );
  }
}
```

このようにHTMLの属性と同じ形式で書くと子コンポーネントにプロパティを渡すことができます。子コンポーネントからは`this.props.author`という形で参照できます。また、JSXの子ノード(この場合`This is one comment`など)は`this.props.children`という予約属性で参照することができます。

## Markdown機能を追加する
Markdown記法でコメントできるようにするためコメント本文のMarkdownを変換するようにします。

`components/Comment.js`を次のように編集してください。

```js:components/Comment.js
import Marked from 'marked'
import React from 'react'

export default class Comment extends React.Component {
  render() {
    var rawMarkup = Marked(this.props.children.toString(), {sanitize: true});
    return(
      <div className='comment'>
        <h3 className='commentAuthor'>
          {this.props.author}
        </h3>
        <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
      </div>
    );
  }
}
```

Markdownの変換にはmarkedライブラリを使っています。`{}`を使うとHTMLタグがエスケープされるため`dangerouslySetInnerHTML`という属性を使います。

## データモデルと連携する
これまではコメントデータを直接ソースコードに埋め込んでいました。最終的な目標はコメントデータをサーバーから取得することです。それを想定したデータモデルからコメントをレンダリングするようにしていきます。

まずはデータモデルを定義してルートコンポーネントにプロパティとして渡します。

```js:index.js
import React from 'react'
import CommentBox from './components/CommentBox'

var data = [
  {author: 'Pete Hunt', text: 'This is one comment'},
  {author: 'Jordan Walke', text: 'This is *another* comment'}
];

React.render(
  <CommentBox data={data} />,
  document.getElementById('container')
);
```

`CommentBox`ではそれを受けて`CommentList`にそのまま渡します。

```js:components/CommentBox.js
import React from 'react'
import CommentList from './CommentList'
import CommentForm from './CommentForm'

export default class CommentBox extends React.Component {
  constructor(props) {
    super(props)
  }

  render() {
    return(
      <div className='commentBox'>
        <h2>Comments</h2>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    );
  }
}
```

`CommentList`では受け取ったプロパティから`Comment`コンポーネントを作成します。

```js:components/CommentList.js
import React from 'react'
import Comment from './Comment'

export default class CommentList extends React.Component {
  render() {
    var commentNodes = this.props.data.map((comment)=> {
      return(<Comment author={comment.author}>{comment.text}</Comment>);
    });
    return(<div className='commentList'>{commentNodes}</div>);
  }
}
```

これでサーバーからデータを受け取る準備ができました。

## サーバーからデータを取得する
ソースコードに直接埋め込んでいたコメントデータをサーバーから取得するようにします。

まず`index.js`のハードコーディング部分を取り除き、`CommentBox`コンポーネントにデータ取得用のURLを渡すようにします。

URLは`server.js`で設定してあります。すべて書くと`http://localhost:8080/comments.js`になります。

```js:index.js
import React from 'react'
import CommentBox from './components/CommentBox'

React.render(
  <CommentBox url="comments.json" />,
  document.getElementById('container')
);
```

サーバーからデータを取得するとReactのコンポーネントたちは動的に変化することになります。コンポーネントの動的な状態を表現するためReactには`state`というものがあります。

プロパティは親から渡されるImmutableな値であり親の所有物です。`state`は自身の所有物でありMutableに扱うことができます。

サーバーから取得したデータを`state`で扱い、動的にコンポーネントが変化できるようにしていきます。

サーバーがとりあえずJSONを返せるようにするため、まずは静的なJSONファイルを作成してください。

```json:comments.json
[
  {"author": "Pete Hunt", "text": "This is one comment"},
  {"author": "Jordan Walke", "text": "This is *another* comment"}
]
```

http://localhost:8080/comments.json にアクセスしてJSONが返っていればOKです。

次はサーバーからデータを取得するようにします。データ取得は`CommentBox`が行います。

```js:components/CommentBox.js
import React from 'react'
import CommentList from './CommentList'
import CommentForm from './CommentForm'
import $ from 'jquery'

export default class CommentBox extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      data: []
    };
  }

  componentDidMount() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: (data) => { this.setState({data: data}); },
      error: (xhr, status, err) => {
        console.error(this.props.url, status, err.toString());
      }
    });
  }

  render() {
    return(
      <div className='commentBox'>
        <h2>Comments</h2>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
}
```

変更点は3つあります。

1つ目はコンストラクタです。`state`の初期化を行っています。このように`state`は`this.state`で参照することができます。

2つ目は`componentDidMount()`メソッドが新しく定義されていることです。このメソッドはコンポーネントがレンダリングされた時にReactが自動で呼び出すメソッドです。Reactでは`this.setState()`メソッドが呼ばれると新しく`state`を設定すると同時に再レンダリングを行います。この`componentDidMount()`メソッドはサーバーからデータを取得してそのデータを元にコンポーネントの再レンダリングを行っています。

3つ目は`CommentList`コンポーネントをレンダリングするときに`this.props.data`ではなく`this.state.data`を渡していることです。

これでサーバーからデータを取得してレンダリングを行えるようになりました。

## 自動で更新させる
サーバーからデータを取得するようになりましたが、取得するのは`index.html`にアクセスした時の1回のみです。他の人がコメントを投稿した時など自動で画面を更新したいので、定期的にサーバーからデータを取得するようにしましょう。

まずは`index.js`から`CommentBox`に何秒ごとにデータを取得するかを伝えます。

```js:index.js
import React from 'react'
import CommentBox from './components/CommentBox'

React.render(
  <CommentBox url="comments.json" pollInterval={2000} />,
  document.getElementById('container')
);
```

次に`CommentBox`で`setInterval`を使って定期的にサーバーからデータを取得するようにします。データ取得部分は新しく`loadCommentsFromServer`というメソッドにしました。

```js:components/CommentBox.js
import React from 'react'
import CommentList from './CommentList'
import CommentForm from './CommentForm'
import $ from 'jquery'

export default class CommentBox extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      data: []
    };
  }

  loadCommentsFromServer() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: (data) => { this.setState({data: data}); },
      error: (xhr, status, err) => {
        console.error(this.props.url, status, err.toString());
      }
    });
  }

  componentDidMount() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer.bind(this), this.props.pollInterval);
  }

  render() {
    return(
      <div className='commentBox'>
        <h2>Comments</h2>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
}
```

これで定期的なポーリングで`setState`を実行してデータの更新があると再レンダリングするようになります。`comments.json`を書き換えてみると2秒以内に自動で画面が更新されるはずです。

## コメント投稿機能を追加する
### フォーム作成
ブラウザからコメントを投稿できるようにしていきます。まずは`CommentForm`コンポーネントにフォームを作成しましょう。

```js:components/CommentForm.js
import React from 'react'

export default class CommentForm extends React.Component {
  render() {
    return(
      <form className="commentForm">
        <input type="text" placeholder="Your name" />
        <input type="text" placeholder="Say something..." />
        <input type="submit" value="Post" />
      </form>
    );
  }
}
```

これからこのフォームに「Postボタンがクリックされるとサーバーにデータを送信する」という機能をつけていきます。

### onSubmitハンドラ

まずはクリックされるとフォームのデータを読み取ってフォームをクリアするという機能をつけてみます。

```js:components/CommentForm.js
import React from 'react'

export default class CommentForm extends React.Component {
  handleSubmit(e) {
    e.preventDefault();
    var author = React.findDOMNode(this.refs.author).value.trim();
    var text = React.findDOMNode(this.refs.text).value.trim();
    if (!text || !author) return;
    // TODO: サーバにデータを送信
    React.findDOMNode(this.refs.author).value = '';
    React.findDOMNode(this.refs.text).value = '';
  }

  render() {
    return(
      <form className="commentForm" onSubmit={this.handleSubmit.bind(this)}>
        <input type="text" placeholder="Your name" ref="author" />
        <input type="text" placeholder="Say something..." ref="text" />
        <input type="submit" value="Post" />
      </form>
    );
  }
}
```

`form`コンポーネントに`onSubmit`ハンドラとして`handleSubmit`メソッドを登録しています。これでPostボタンをクリックした時`handleSubmit`メソッドが実行されます。

`handleSubmit`では次の流れで処理を行います。

* (ブラウザのSubmit処理を無効化する)
* フォームからデータを取得する
* サーバーにデータを送信する
* フォームをクリアする

1つ目の「ブラウザのSubmit処理を無効化する」は`e.preventDefault()`の部分です。Submitイベントへのブラウザのデフォルト処理をキャンセルしています。

2つ目の「フォームからデータを取得する」は

```js
var author = React.findDOMNode(this.refs.author).value.trim();
```

の部分です。Reactコンポーネントに対して`ref`属性で名前をつけておけば`this.refs`からコンポーネントを参照できるようになります。そして`React.findDOMNode`を使えば実際のブラウザのDOM要素を取得できます。

3つ目はこれから実装します。

4つ目の「フォームをクリアする」は

```js
React.findDOMNode(this.refs.author).value = '';
```

の部分です。ここでも`this.refs`を使っています。

### バインディング

`form`コンポーネントに`this.handleSubmit`を渡すとき`.bind(this)`しています。これはその関数内の`this`を固定するものです。これにより`handleSubmit`内の`this`を`CommentForm`コンポーネントに固定しているため、関数内で`this.refs`が使えています。

従来のReactでは不要でしたが、ES6のReact.Componentクラスの継承では暗黙的に`this`が継承されないため必要になります。[^1]

[^1]: https://facebook.github.io/react/blog/2015/01/27/react-v0.13.0-beta-1.html#autobinding

### データ送信

ではデータ送信部分を実装していきます。が、その前に新しいコメントの画面反映について考えます。

新しいコメントが投稿された時にはコメントデータをリフレッシュして画面に反映する必要があります。コメントデータをStateで保持しているのは`CommentBox`コンポーネントなのでなんらかの方法で`CommentBox`にデータを送る必要があります。

```
- CommentBox
  - CommentList
    - Comment
  - CommentForm
```

これまでは親から子へとpropsでデータを渡してきました。今回は逆方向にデータを渡すことになります。Reactでは親から子へコールバック関数を渡すことでこれを実現します。

まずは`CommentBox`でコールバック関数`handleCommentSubmit`を定義して`props`で`CommentForm`に渡します。

```js:components:CommentBox.js
import React from 'react'
import CommentList from './CommentList'
import CommentForm from './CommentForm'
import $ from 'jquery'

export default class CommentBox extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      data: []
    };
  }

  loadCommentsFromServer() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      cache: false,
      success: (data) => { this.setState({data: data}); },
      error: (xhr, status, err) => {
        console.error(this.props.url, status, err.toString());
      }
    });
  }

  handleCommentSubmit(comment) {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: (data) => { this.setState({data: data}); },
      error: (xhr, status, err) => {
        console.error(this.props.url, status, err.toString());
      }
    });
  }

  componentDidMount() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer.bind(this), this.props.pollInterval);
  }

  render() {
    return(
      <div className='commentBox'>
        <h2>Comments</h2>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit.bind(this)} />
      </div>
    );
  }
}
```

`CommentForm`では受け取ったコールバック関数を実行します。

```js:components/CommentForm.js
import React from 'react'

export default class CommentForm extends React.Component {
  handleSubmit(e) {
    e.preventDefault();
    var author = React.findDOMNode(this.refs.author).value.trim();
    var text = React.findDOMNode(this.refs.text).value.trim();
    if (!text || !author) return;
    this.props.onCommentSubmit({author: author, text: text});
    React.findDOMNode(this.refs.author).value = '';
    React.findDOMNode(this.refs.text).value = '';
  }

  render() {
    return(
      <form className="commentForm" onSubmit={this.handleSubmit.bind(this)}>
        <input type="text" placeholder="Your name" ref="author" />
        <input type="text" placeholder="Say something..." ref="text" />
        <input type="submit" value="Post" />
      </form>
    );
  }
}
```

これでPostボタンをクリックするとサーバーにデータを送信して画面を更新するようになりました。ブラウザで試してみてください。

## 投稿時の画面表示を最適化する
アプリケーションに必要な機能はひと通り揃いました。しかし現在の実装ではPostをクリックしてサーバーのレスポンスがあるまで画面が更新されません。そのためサーバーが遅い時はアプリケーション自体も遅く感じます。

アプリの体感速度を向上させるためPostをクリックした瞬間画面が更新されるように変更します。`handleCommentSubmit`に3行追加します。

```js:components/CommentBox.js
...

  handleCommentSubmit(comment) {
    var comments = this.state.data;
    var newComments = comments.concat([comment]);
    this.setState({data: newComments});
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: (data) => { this.setState({data: data}); },
      error: (xhr, status, err) => {
        console.error(this.props.url, status, err.toString());
      }
    });
  }

...
```

# おわりに
おわりです。公式のチュートリアルをちょっとES6っぽくしてみました。

Learn more:

* [Why to use React](http://facebook.github.io/react/docs/why-react.html)
* [API reference](http://facebook.github.io/react/docs/top-level-api.html)

とのことです。