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

  • 591
    いいね
  • 8
    コメント
この記事は最終更新日から1年以上が経過しています。

はじめに

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

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

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

環境

  • Mac OSX
  • Node.js v0.12.7
  • Gulp 3.9.0
  • React 0.13.3

作るもの

公式のTutorialに沿って、シンプルなコメントボックスをReactで作成します。

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

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

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

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

準備

nodeインストール

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

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

参考: Mac OS Xに**env管理ツールのanyenvをインストール

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

anyenv install ndenv
exec $SHELL -l

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

ndenv install v0.12.7
ndenv global v0.12.7

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

gulpインストール

gulpはビルドツールです。今イケてるらしいです。GuardとかGruntとかの仲間ですね。

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

npm install -g gulp
ndenv rehash

ディレクトリ作成

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

mkdir react_es6
cd react_es6

package.json作成

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

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

$ 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コマンドで追加します。

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はこのようになります。
バージョンは異なるかもしれません。

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を用意しておきます。

touch comments.json

次にサーバーアプリケーションserver.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に書きます。

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の詳しいことは次の記事などを参照してください。

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

Getting Started

index.htmlを作る

まずはブラウザに表示されるindex.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を作成してください。

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を起動してください。

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つは同じです。

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

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

コンポーネントを組み合わせる

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

index.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')
);

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

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

ファイルを分割する

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

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

mkdir components

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

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>
    );
  }
}
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>
    );
  }
}
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は次のようになります。

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

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

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

プロパティを使う

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

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

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です。

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を次のように編集してください。

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という属性を使います。

データモデルと連携する

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

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

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にそのまま渡します。

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コンポーネントを作成します。

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になります。

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ファイルを作成してください。

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が行います。

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の初期化を行っています。このようにstatethis.stateで参照することができます。

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

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

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

自動で更新させる

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

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

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

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

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

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コンポーネントにフォームを作成しましょう。

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ハンドラ

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

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つ目の「フォームからデータを取得する」は

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

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

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

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

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

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

バインディング

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

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

データ送信

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

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

- CommentBox
  - CommentList
    - Comment
  - CommentForm

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

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

components
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では受け取ったコールバック関数を実行します。

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行追加します。

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:

とのことです。