チュートリアル

これから、シンプル且つブログ内に配置可能な実用的なコメント・ボックスを構築します。 Disqus、LiveFyre、またはFacebookコメントによって提供されるようなリアルタイム・コメントの基本的なものになります。

このコメント・ボックスは次の機能を提供します。

  • 全てのコメント表示
  • コメントを送信するフォーム
  • バックエンドのカスタマイズを提供するためのフック

また、幾つかの洗練された機能も持ち合わせます。

楽観的コメント投稿:
コメントはサーバーに保存される前にリスト内に表示されます。 そのため、コメントの反映を早く感じることでしょう。
ライブ更新:
他のユーザーのコメントが、リアルタイムに表示されます。
マークダウン・フォーマット:
ユーザーはマークダウンのフォーマットを使用することが可能です。

チュートリアルをスキップしてソースコードを参照したい場合

GitHub - reactjs/react-tutorial

サーバーの実行

このチュートリアル始めるにあたり、必須ではありませんが、 後ほど実行サーバーへのPOSTが必要になる機能を追加することになります。 もし、あなたが使い慣れた自身のサーバーで構築したいのであれば、それを利用して下さい。 サーバーサイドを気にする事無くReactの学習に集中したい方のために、 我々は様々な言語 - JavaScript(Node.jsを使用)、Python、Ruby、Go、PHP - で書かれたシンプルなサーバーを用意しています。 これらは全てGitHubで利用可能で、ソースコードを見ることも、 始めるためにzipファイルをダウンロードすることも可能です。

チュートリアルを始めるために、まずpublic/index.htmlを編集します。

<!-- index.html -->
<html>
  <head>
    <title>Hello React</title>
    <script src="https://fb.me/react-0.13.1.js"></script>
    <script src="https://fb.me/JSXTransformer-0.13.1.js"></script>
    <script src="https://code.jquery.com/jquery-2.1.3.min.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/jsx">
      // あなたのコードをここに書きます。
    </script>
  </body>
</html>

以降のこのチュートリアルでは、このscriptタグ内にJavaScriptを書いていくことになります。

注意:
Ajax呼び出しのコードをシンプルにしたいために、jQueryを含めていますが、 Reactを動作させるために強制されるものではありません。

初めてのコンポーネント

Reactはモジューラー、構成可能なコンポーネントが全てです。 このコメント・ボックスの例では、下記のコンポーネント構成を持ちます。

- CommentBox
  - CommentList
    - Comment
  - CommentForm

それでは、<div>だけの、シンプルなコメントボックスのコンポーネントを構築してみましょう。

// tutorial1.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
React.render(
  <CommentBox />,
  document.getElementById('content')
);
JSX文法

まず、JavaScriptの中にXML的な文法がある事に気づくでしょう。 このシンタックスシュガーを、下記の素のJavaScriptに変換するシンプルなプリコンパイラが用意されています。

// tutorial1-raw.js
var CommentBox = React.createClass({displayName: 'CommentBox',
  render: function() {
    return (
      React.createElement('div', {className: "commentBox"},
        "Hello, world! I am a CommentBox."
      )
    );
  }
});
React.render(
  React.createElement(CommentBox, null),
  document.getElementById('content')
);

JSXとプリコンパイラの使用は任意ではありますが、素のJavaScriptよりもJSX文法の方が簡単であると自負しています。 詳細については、JSX文法の記事を参照してください。

What's going on

React.createClass()が新しいReactコンポーネントを作成するために、 JavaScriptオブジェクト内に幾つかのメソッドを渡します。 最も重要なメソッドはrenderであり、最終的にHTMLに描画するReactコンポーネントのツリーを返すために呼び出されます。

<div>タグは実際のDOMノードでは無く、 Reactのdivコンポーネントがインスタンス化されたものになります。 あなたはReactが扱うマークアップまたはデータのことが気になるかもしれませんが、Reactは安全です。 XSSを防御するために、デフォルトでReactはHTML文字列を生成しません。

基本的なHTMLを返す必要は無く、 あなたが構築したコンポーネントのツリーを返すことが出来ます。 これによってReactは構成可能なものとなり、フロントエンドをメンテナンスする際の基本理念となります。

React.render()はルートコンポーネントをインスタンス化して、フレームワークを開始し、 提供されたマークアップを2つ目の引数として提供された生のDOM要素に注入します。

コンポーネントの構成

再び、シンプルな<div>のCommentListとCommentFormのスケルトンを組み立ててみましょう。

// tutorial2.js
var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        Hello, world! I am a CommentList.
      </div>
    );
  }
});

var CommentForm = React.createClass({
  render: function() {
    return (
      <div className="commentForm">
        Hello, world! I am a CommentForm.
      </div>
    );
  }
});

次に、これらの新しいコンポーネントを使用するために、CommentBoxコンポーネントを更新します。

// tutorial3.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList />
        <CommentForm />
      </div>
    );
  }
});

HTMLタグとコンポーネントが混合していることに注目して下さい。 HTMLコンポーネントは、1つの違いを除き、あなたが定義したような通常のReactコンポーネントです。 JSXコンパイラは自動的にHTMLタグをReact.createElement(tagName)式に書き換え、 その他はそのままにします。(翻訳に自信なし) これは、グローバルな名前空間の汚染を防ぐために行います。

propsの使用

親から渡されるデータに依存するCommentコンポーネントを作成してみましょう。 親コンポーネントから渡されるデータは、子コンポーネント上で'プロパティ'として利用可能です。 これらの'プロパティ'はthis.propsを通してアクセス可能です。 propsを使用することで、CommentListからCommentに渡されるデータを読み込み、 マークアップを描画することが可能になります。

// tutorial4.js
var Comment = React.createClass({
  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {this.props.children}
      </div>
    );
  }
});

JSX(属性または子のいずれいか)内でJavaScript式を括弧で囲むことで、 ツリー内にテキストやReactコンポーネントを渡すことが出来ます。 this.propsのキーとしてコンポーネントに渡された名付けされた属性と、 this.props.childrenとする入れ子の要素にアクセスします。

コンポーネントのプロパティ

ここまでで、Commentコンポーネントが定義されたので、 投稿者名(author)とコメントテキストを渡してみましょう。 こうすることで、各コメントで重複するコードの再利用を可能にしてくれます。 それでは、CommentListにコメントを追加してみましょう。

// tutorial5.js
var CommentList = React.createClass({
  render: function() {
    return (
      <div className="commentList">
        <Comment author="Pete Hunt">This is one comment</Comment>
        <Comment author="Jordan Walke">This is *another* comment</Comment>
      </div>
    );
  }
});

マークダウンの追加

マークダウン記法は、テキストをインラインでフォーマットするための簡単な方法です。 例えば、アスタリスクでテキストを囲むと、そのテキストは強調されます。

まず、サードパーティのライブラリをアプリケーションに追加します。 これはマークダウンのテキストを受け取って生のHTMLに変換するJavaScriptのライブラリです。 head内にscriptタグが必要になります。(Reactのプレイグラウンドには既に含まれています。)

<!-- index.html -->
<head>
  <title>Hello React</title>
  <script src="https://fb.me/react-0.13.1.js"></script>
  <script src="https://fb.me/JSXTransformer-0.13.1.js"></script>
  <script src="https://code.jquery.com/jquery-1.10.0.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.2/marked.min.js"></script>
</head>

次に、コメントテキストをマークダウンに変換して出力してみましょう。

// tutorial6.js
var Comment = React.createClass({
  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        {marked(this.props.children.toString())}
      </div>
    );
  }
});

ここで行ったことは、markedライブラリの呼び出しです。 Reactにラップされたテキストから、this.props.childrenを生の文字列に変換して、 markedに理解させる必要があるため、明示的にtoString()を呼び出します。

ただし、これには問題があります。 描画されたコメントはブラウザ上で「"<p>This is <em>another</em> comment</p>"」のように表示されます。 これらをHTMLとして正しく描画させる必要があります。

これは、ReactがXSSによる攻撃を防ぐためです。 これを回避する方法は存在しますが、フレームワークが警告を発することになります。

// tutorial7.js
var Comment = React.createClass({
  render: function() {
    var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={{__html: rawMarkup}} />
      </div>
    );
  }
});

これは意図的に生のHTMLを挿入することを困難にする特別なAPIですが、 markedのためにこのバックドアを利用します。

注意: この機能を使用することで、markedにソースを依存させることになる事を覚えておいて下さい。 このケースでは、変更無しに渡す代わりに、 markedにsanitize: trueを渡すことで、 ソース内のHTMLのマークアップをエスケープするように伝えます。

データモデルとの連携

ここまでに、ソースコードに直接コメントを挿入してきました。 代わりに、JSONデータをコメントリストに入れて描画してみましょう。 最終的にはサーバーから取得したものを使用しますが、 今のところは下記のようにしてJSONデータを用意します。

// tutorial8.js
var data = [
  {author: "Pete Hunt", text: "This is one comment"},
  {author: "Jordan Walke", text: "This is *another* comment"}
];

CommentListに入れるこのデータの取得は、モジュール形式にする必要があります。 propsを介してこのデータをCommentListに渡すために、 CommentBoxとこのReact.render()の呼び出しを変更します。

// tutorial9.js
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.props.data} />
        <CommentForm />
      </div>
    );
  }
});

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

CommentList内でデータが利用可能になったので、 直接コメントを描画してみましょう。

// tutorial10.js
var CommentList = React.createClass({
  render: function() {
    var commentNodes = this.props.data.map(function (comment) {
      return (
        <Comment author={comment.author}>
          {comment.text}
        </Comment>
      );
    });
    return (
      <div className="commentList">
        {commentNodes}
      </div>
    );
  }
});

サーバーからの取得

ハードコードされたデータをサーバーから取得したデータに置き換えてみましょう。 データ用のpropを削除して、サーバーから取得するためのURLを指定したものに置換します。

// tutorial11.js
React.render(
  <CommentBox url="comments.json" />,
  document.getElementById('content')
);

このコンポーネントは以前のコンポーネントとは自身で再描画しなければいけないため、 以前のコンポーネントとは異なります。 コンポーネントは、サーバーに対してリクエストを送り、データが返されるまではデータを持つことが出来ず、 その時点では新しいコメントを描画する必要があるかもしれません。

Reactiveなstate

これまでに、各コンポーネントはpropsを基に自身を描画していました。 propsは不変(immutable)です。親から"親が所有する"ものとして渡されます。 データを双方で変更出来るように、変更(mutable)状態をコンポーネントに導入します。 this.stateはコンポーネントでプライベートなものであり、 this.setState()の呼び出しによって変更することが可能です。 stateが更新されると、コンポーネントは自分自身を再描画します。

render()メソッドは、 this.propsthis.stateの命令文/処理文(declaratively)のように書かれます。 このフレームワークは、UIが入力と一致している事を保証します。

サーバーがデータを取得すると、コメントデータが変更される必要があります。 コメントのデータの配列をCommentBoxコンポーネントに、 そのstateとして追加してみましょう。

// tutorial12.js
var CommentBox = React.createClass({
  getInitialState: function() {
    return {data: []};
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

getInitialState()はコンポーネントのライフサイクル間で、確実に一度だけ実行され、 コンポーネントの初期状態のセットアップを行います。

stateの更新

コンポーネントが最初に作られる際に、 サーバーからJSONをGETして、状態(state)を更新して最新のデータを反映する必要があります。 実際のアプリケーションではこれは動的なエンドポイントになりますが、 この例では静的なJSONファイルを使用して、シンプルな状態を維持します。

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

サーバーへの非同期リクエストには、jQueryを使用します。

注意:
AJAXアプリケーションの作成に入っていくため、ファイルシステム上では無く、 Webサーバーを使用した開発が必要になります。 前述したように、我々はGitHub上で、 あなたが使用することの出来る幾つかのサーバーのプログラムを提供しています。 これらは、この後のチュートリアルで必要となる環境を提供します。

// tutorial13.js
var CommentBox = React.createClass({
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

componentDidMountは、Reactによってコンポーネントが描画された際に自動的に呼び出されるメソッドです。 動的に更新するには、this.setState()を呼び出します。 サーバーから取得したものをコメントの古い配列と置換え、UIは自動的に自身の更新を行います。 このようにReactivity(反応的)であるため、これがライブ更新(live updates)を追加する唯一の変更になります。 ここではシンプルなポーリングを使用しますが、実践であればWebSocketsなどのテクノロジーを使用するのが良いでしょう。

// tutorial14.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm />
      </div>
    );
  }
});

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

ここでは、AJAX呼び出しを別のメソッドに移動して、 コンポーネントの最初の読み込み時とその後2秒毎に呼び出すようにしました。 ブラウザ上で実行して、comments.jsonファイルを変更してみましょう。 2秒後に変更されたものが表示されるはずです。

新しいコメントの追加

ここからは、フォームの構築をしていきます。 CommentFormコンポーネントは、ユーザーに彼らの名前とコメントテキストの入力を促し、 サーバーにリクエストを送り、そのコメントを保存します。

// tutorial15.js
var CommentForm = React.createClass({
  render: function() {
    return (
      <form className="commentForm">
        <input type="text" placeholder="Your name" />
        <input type="text" placeholder="Say something..." />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

フォームをインタラクティブなものにしていきましょう。 ユーザーがフォームを送信した際に、 そのフォームを空にしてサーバーへリクエストを送り、コメントのリストをリフレッシュします。 これを実装するために、まずはフォームの送信イベントをリッスンし、入力欄をクリアしましょう。

// tutorial16.js
var CommentForm = React.createClass({
  handleSubmit: function(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 = '';
    return;
  },
  render: function() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input type="text" placeholder="Your name" ref="author" />
        <input type="text" placeholder="Say something..." ref="text" />
        <input type="submit" value="Post" />
      </form>
    );
  }
});
Events

Reactはキャメルメースの名付け形式で、コンポーネントにイベントハンドラを割り当てます。 ここでは、正しい入力でフォームが送信された際に、 フォームの入力欄をクリアするonSubmitハンドラをフォームに割り当てています。

イベント上でpreventDefault()を呼び出すことで、 フォームが送信された際のブラウザのデフォルトアクションが実行されるのを防いでいます。

Refs

ref属性を使用して子コンポーネントへ名前を割り当て、 this.refsでそのコンポーネントを参照します。 コンポーネント上でReact.findDOMNode(component)を呼び出すことで、 ブラウザのネイティブなDOM要素を取得することが出来ます。

Callbacks as props

ユーザーによるコメントの送信時に、コメントのリストをリフレッシュし、新しいコメントをリストに追加する必要があります。 CommentBoxは、コメントのリストを表す状態(state)を所有するため、 CommentBox内で、このロジックの全てを行うようにするのは理にかなっていると言えます。

子コンポーネントから返されたデータを、その親に渡す必要があります。 これを親のrenderメソッド内で行うために、 子のonCommentSubmitイベントへバインドさせることで、 新たなコールバック(handleCommentSubmit)を子に渡します。 イベントがトリガされる度に、このコールバックが実行されます。

// tutorial17.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(comment) {
    // TODO : サーバーへ送信し、リストをリフレッシュする
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});

ユーザーがフォームを送信した際に、 CommentFormからコールバックを呼び出すようにしましょう。

// tutorial18.js
var CommentForm = React.createClass({
  handleSubmit: function(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 = '';
    return;
  },
  render: function() {
    return (
      <form className="commentForm" onSubmit={this.handleSubmit}>
        <input type="text" placeholder="Your name" ref="author" />
        <input type="text" placeholder="Say something..." ref="text" />
        <input type="submit" value="Post" />
      </form>
    );
  }
});

コールバックが適切な場所に配置されたので、 サーバーへの送信とリストのリフレッシュの処理を実装しなければいけません。

// tutorial19.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(comment) {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      type: 'POST',
      data: comment,
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});

最適化: 楽観的な更新

アプリケーションの必要な機能は一通り揃いましたが、コメントがリスト内に表示される直前、 リクエストが完了するまで待たされるため遅く感じてしまいます。 コメントのリストへの追加を"楽観的"にすることで、 アプリケーションの動作を速く感じるようにします。

// tutorial20.js
var CommentBox = React.createClass({
  loadCommentsFromServer: function() {
    $.ajax({
      url: this.props.url,
      dataType: 'json',
      success: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  handleCommentSubmit: function(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: function(data) {
        this.setState({data: data});
      }.bind(this),
      error: function(xhr, status, err) {
        console.error(this.props.url, status, err.toString());
      }.bind(this)
    });
  },
  getInitialState: function() {
    return {data: []};
  },
  componentDidMount: function() {
    this.loadCommentsFromServer();
    setInterval(this.loadCommentsFromServer, this.props.pollInterval);
  },
  render: function() {
    return (
      <div className="commentBox">
        <h1>Comments</h1>
        <CommentList data={this.state.data} />
        <CommentForm onCommentSubmit={this.handleCommentSubmit} />
      </div>
    );
  }
});

Congrats!

少しの手順でコメントボックスを構築することが出来ました。 更なる学習のために、何故Reactを使用するのかについて学び、 APIリファレンスを読んでハッキングしてみて下さい!グッドラック!

 Back to top

© 2013-2017 Facebook Inc.
Documentation licensed under CC BY 4.0.

このページは、ページトップのリンク先のReact内のページを翻訳した内容を基に構成されています。 下記の項目を確認し、必要に応じて公式のドキュメントをご確認ください。 もし、誤訳などの間違いを見つけましたら、 @tomofまで教えていただければ幸いです。

  • ドキュメントの情報が古い可能性があります。
  • "訳注:"などの断わりを入れた上で、日本人向けの情報やより分かり易くするための追記を行っている事があります。