新人プログラマーが「React」を使ってリアルタイムコメント機能を作ってみた(前編)

  • このエントリーをはてなブックマークに追加
  • Evernoteに保存

みなさまはじめまして。リスペクトの丹野です。

去年の4月に入社し、今年で2年目を迎えました新人プログラマーです。どうかお手柔らかによろしくお願いいたします。

さて、最近js関係の記事でよくReactという名前を見かけます。
何度も見かけるのでとても気になっていました。
しかし、使いこなせるだろうか…敷居が高そうだなぁと思って今まで手を出せずにいました。
そこで今回はReactに実際に触れてみてどんなものなのかを体験しつつ、その所感をレポートしたいと思います。

Reactとは

facebook製のJavaScriptライブラリです。
http://facebook.github.io/react/

Backbone.jsやAngular.jsのようなフレームワークとは少し違い、MVCのViewの部分を作るためのライブラリです。
facebookをはじめ、Instagramなどいろいろなところで使われるようになってきています。
あくまでライブラリなのでBackbone.jsやAngular.jsなどのフレームワークと併用することも可能です。
Backbone.jsやAngular.jsはアプリケーションの規模が大きくなると管理が難しくなりますが、Reactは規模が大きくなっても管理がしやすいというのが大きなメリットです。

Reactの特徴として公式サイトで紹介されているものは以下になります。

JUST THE UI

ReactはMCVのViewを作るためのライブラリなので、ModelやControllerについて考えることなく手軽に導入することができます。

VIRTUAL DOM

VIRTUAL DOMとは、実際のDOMとは別に仮想のDOMを構築して、前回の仮想DOMとの差分だけをとって更新し、実際のDOMに反映することができるというものです。DOMを更新するよりも高速に処理できるところが特徴です。

DATA FLOW

Reactでは、データの受け渡しが親から子への一方向なので処理の流れがつかみやすくなります。なのでアプリケーションもわかりやすく作ることができます。

まとめると、今までのライブラリに比べると簡単に実装できて、処理が早く、コード全体の流れも見やすくなってコードの管理が楽になる、というのがReactの特徴ですね。

導入方法

主な導入方法は以下になります。

<script src="https://fb.me/react-15.1.0.js"></script>
<script src="https://fb.me/react-dom-15.1.0.js"></script>

チュートリアルでは後者ですが、好きな方で試していいと思います。
これだけ入れてしまえば使えるのでとっても手軽です。

記事作成中に気がついたらReact.jsのバージョンが上がっていたので、こまめにバージョン情報を見ておくといいかもしれません。

とりあえずチュートリアルをやってみた

今回はReactがどんなものなのか知るために、公式サイトにチュートリアルが載っていたのでとりあえずこのチュートリアルをなぞっていくことにしました。
Tutorial

ちなみに日本語ページが存在するのですが
チュートリアル
内容が若干違うようなので、日本語ページは参考程度に見ることをおすすめします。

内容としてはブログにも設置できるようなリアルタイムコメントボックスをつくるというものです。
チュートリアルにも記載されていますが、DisqusやLiveFyre、Facebook commentsなどのコメントシステムが採用しているリアルタイムコメント機能のベーシック版です。以下のような機能を基本装備しています。

  • コメント全件の表示欄
  • コメントの送信フォーム
  • 自作のバックエンドとの連携機能

そこに以下の機能を追加します。

  • コメントの先読み:送信したコメントをサーバに保存する前に表示することで、アプリ動作の体感速度をあげる。
  • 自動更新:他のユーザのコメントをリアルタイムで表示させる。
  • Markdown記法:ユーザがMarkdown記法でコメントを書けるようにする。

ちなみに実際にコメントボックスとして動かすにはサーバサイドのプログラムも必要なのですが、そこらへんも既に出来上がったコードが用意されているのでReactで画面を作ったらすぐに動かすことができます。しかもPython、Ruby、Go、PHPと好きな環境で試せるそうです。至れり尽くせりですね。

はじめてみよう

それではさっそくチュートリアルをやってみましょう。
今回用意した環境は以下のとおりです。

  • CentOS 6.5
  • Apache 2.2.15
  • React 15.1.0

まずHTMLファイルを用意します。
HTMLに関してはチュートリアルに載っているものをそのまま使用しました。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>React Tutorial</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.5/marked.min.js"></script>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/babel" src="scripts/example.js"></script>
    <script type="text/babel">
      // To get started with this tutorial running your own code, simply remove
      // the script tag loading scripts/example.js and start writing code here.
    </script>
  </body>
</html>

最初のコンポーネント

Reactの特徴とも言えるコンポーネントを作っていきます。
Reactでは画面を構成する要素をコンポーネントという部品にわけ、それを組み立てることでアプリケーションを構築しています。
コンポーネントをつくることで、カプセル化ができ、保守性や可読性が向上するというメリットがあります。
(参考:React.js 実戦投入への道

今回作るコメントボックスのコンポーネント構造は以下のようになります。

  • CommentBox
    • CommentList
      • Comment
    • CommentForm

上の構造はこんなイメージでしょうか。

<div class="commentBox">
    <div class="commentList">
        <div class="comment"></div>
    </div>
    <div class="commentForm">
    </div>
</div>

まず最初のコンポーネントを書いてみます。

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

ReactDOM.render(
  <CommentBox />,
  document.getElementById('content')
);

CommentBoxコンポーネントを作成しました。
下のReactDOM.render()で実際のDOMに反映しています。
第一引数で反映する内容、第二引数(document.getElementById(‘content’))でHTMLの反映場所を指定しているようですね。

イメージ的に<div className="commentBox></div>が変換されて<div class="commentBox"></div>になる、という感じでしょうか。

ちなみに、このReactDOM.render()は一番下に置いておくのがベストらしいので、この先のコードはすべてこの上に足していくことにします。

コンポーネントの組み立て

ここでさらにcommentListとcommentFormというコンポーネントを追加します。

var CommentList = React.createClass({
    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コンポーネントを書き直します。

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

これでコメントをリスト表示するためのCommentListコンポーネントとコメントを投稿するためのCommentFormコンポーネントが出来上がりました。

Propsを使う

入力されたコメントを表示するCommentコンポーネントを作ります。
Commentコンポーネントは投稿されたコメントを表示するためのコンポーネントなので、コメントのデータを受け取って表示させる処理が必要です。
そのデータの受け渡しを可能にするのがPropsというプロパティです。
自分の親にあたるコンポーネントから渡されたデータを扱えます。
データにアクセスするにはthis.propsを使います。

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 *author* comment</Comment>
      </div>
    );
  }
});

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

Commentの親コンポーネントはCommentListです。
なのでCommentコンポーネントはCommentListコンポーネントからデータを受け取ります。
Commentコンポーネントの{this.props.author}はCommentListコンポーネントのauthorプロパティのデータを取ってきます。
Commentコンポーネントの{this.props.children}はCommentListコンポーネントのネストされた子要素の値をとってきます。今回で言えばThis is one commentThis is *author* commentですね。
これでコメントが表示されるようになりました。

MarkDownの追加

最初に作ったHTMLファイルでheadに既に以下のコードが入っていたと思います。

<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/0.3.5/marked.min.js"></script>

marked.jsとは、ブラウザ上でマークダウンをhtmlに変換してくれるマークダウン用のパーサー&コンパイラです。
(引用:『marked.js』 を使ってブラウザ上で markdown を html に変換する方法

このコンパイラを使ってユーザがマークダウン記法でコメントを書けるようにします。
Commentを以下のように変更します。

var Comment = React.createClass({
  rawMarkup: function() {
    var rawMarkup = marked(this.props.children.toString(), {sanitize: true});
    return {__html: rawMarkup};
  },

  render: function() {
    return (
      <div className="comment">
        <h2 className="commentAuthor">
          {this.props.author}
        </h2>
        <span dangerouslySetInnerHTML={this.rawMarkup()} />
      </div>
    );
  }
});

マークダウンで書かれたコメントは{marked(this.props.children.toString()}で出力できるのですが、このままだとReactがご丁寧にXSS対策を行ってくれているおかげでHTMLタグまで表示されてしまうんですね。
なのでちゃんとHTMLタグとして認識してもらうためにrawMarkup関数を使っています。
ここで気をつけなければいけないのが、dangerouslySetInnerHTMLです。
dangerouslySetInnerHTMLはHTMLをエスケープせずに表示するためのものですがXSSの危険があります。
十分信頼できる場合以外使用するなとtutorialにも記載されているので、使用する際は気をつけて使用してください。

データモデルとの連携

今までソースコードに直接書いていたauthorやcomment本文をデータとして受け取れる形にします。
今回はjsonデータで受け取って表示できるようにします(後々Ajaxとかで使えそうですね!)
まずjson形式で送信するデータを書いてみます。

var data = [
    {id: 1, author: "Pere Hunt", text: "This is one comment"},
    {id: 2, author: "Jordan Walke", text: "This is *author* comment"}
];

CommentBoxを以下のように変更します。

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

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

ReactDOM.render()でdataを受け取り、CommentBoxコンポーネントでpropsを使ってCommentListにデータを渡しています。
あとはこのデータの内容をCommentコンポーネントを使って表示できるようにCommentListに変更を加えます。

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

CommentBoxから受け取ったdataに.map()を使ってcommentという配列をつくり、Commentコンポーネントにデータを渡せるようにしています。
これでCommentコンポーネントを使って1つ1つのコメントデータを表示できるようになりました。
Commentコンポーネントの処理を関数化してCommentListコンポーネントで表示できるようにしています。

前半まとめ

さて!これでJSONデータで表示できるところまで行きました!

ここまで試してみましたが、確かにデータの流れが一方向なので処理の流れも追いやすいですし、renderの中身もHTMLに近いのでHTMLがわかっていれば書くのも非常に簡単そうです。コンポーネントで分けることでコードが整理され、修正も管理も楽そうだなという印象を持ちました。

今回はここまでで一旦終了にしたいと思います。
次回はチュートリアルの最後まで進めて実際にコメントの投稿・表示ができるところまで作ってみたいと思います!

それではまた次回お会いしましょう!

参考

今話題のReact.jsはどのようなWebアプリケーションに適しているか? Introduction To React─ Frontrend Conference
Reactの使い方
React.jsとは

  • このエントリーをはてなブックマークに追加
  • Evernoteに保存