読者です 読者をやめる 読者になる 読者になる

アラタナエンジニアブログ

aratana Engineer's Blog

React - FacebookのオープンソースJavaScript Libraryは、すごいです。

Javascript

f:id:miiitaka:20150309173519j:plain

アラタナエンジニアブログをご覧の皆様こんにちは。高見@フロントエンドエンジニアです。今日は、Facebookが提供しているJavaScriptライブラリReactに触れます。公式サイトにチュートリアルがあるので、それを追いかけてみようと思います。

Reactとは?

ReactはFacebookが2013年にリリースしたオープンソースJavaScriptライブラリです。実際にFacebookInstagramで利用されているライブラリですので、しっかりテストされている安定したライブラリだと思います。FacebookFacebookそのものに実践投入している点を見ると本気度が違う(はず) 他にも導入例として、Netflix、Yahoo、AirbnbSony、Atlassianなどでも使われているようです。

ReactはMVCの「V」を提供

Reactは、Googleが提供しているAngularJSBackborn.jsのようなMVCフレームワークknockout.jsVue.jsのようなMVVMフレームワークとは異なり、MVCの「V」を提供するライブラリです。 Reactの公式サイトのTOPページにも下記のように記載があります。

JUST THE UI
Lots of people use React as the V in MVC. Since React makes no assumptions about the rest of your technology stack, it's easy to try it out on a small feature in an existing project.

早速チュートリアルを追ってみる

Reactの公式サイトに「Getting Started」とありますので、そちらの中のソースコードを使います。通常、jQueryを利用する方が多いと思いますのでそちらと比較しながら進めていきたいと思います。 まず、以下のようなソースコードを準備しました。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>React Example</title>
  <script src="http://code.jquery.com/jquery-1.11.2.min.js"></script>
</head>
<body>
<div id="example"></div>
  <script>
    /* iQuery */
    $('#example').append($('<h1>').text('Hello, world!'));
  </script>
</body>
</html>

id="example"の要素間にh1タグとテキストを挿入する例です。jQueryで実装しようとするとこうなります。これをReactに置き換えると以下のようになります。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>React Example</title>
  <script src="http://fb.me/react-0.12.2.js"></script>
</head>
<body>
<div id="example"></div>
  <script>
    /* React */
    React.render(
      React.createElement('h1', null, 'Hello, world!'),
      document.getElementById('example')
    );
  </script>
</body>
</html>

まず、ライブラリを読み込んでおく必要がありますので、FacebookのCDNから読み込みました。

<script src="http://fb.me/react-0.12.2.js"></script>

React.createElement()で要素を生成。React.render()APIで、生成した要素を指定した要素の間にレンダリングします。 これだけですと「jQueryのほうが簡単なのでは?」と思ってしまいます。Reactを導入するメリットが感じられません...がここからReactが本気出します。

React + JSX

ReactではJSXを使用することができます。これを用いることにより、要素を生成したりする部分をXMLライクに記述することができるようになります。JSXを使用するにはコンパイラが必要です。React-toolsをインストールするとjsxコマンドでコンパイルすることができるようになりますが、今回はオンラインで変換する方法を用います。 以下のファイルを読み込みます。

<script src="build/JSXTransformer.js"></script>

準備ができたら、先ほどのソースコードをJSXで書き直します。 scriptタグにタイプ指定をしましょう。text/jsxを指定する必要があります。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>React Example</title>
  <script src="http://fb.me/react-0.12.2.js"></script>
  <script src="build/JSXTransformer.js"></script>
</head>
<body>
<div id="example"></div>
  <script type="text/jsx">
    /* React + JSX */
    React.render(
      <h1>Hello, world!</h1>,
      document.getElementById('example')
    );
  </script>
</body>
</html>

React.createElement()の箇所を通常のHTMLを記述するような形でできました。この書き方ですと、プログラマーでなくても見た目でわかりやすいです。React+JSXでオンラインで変換をすると、開発ツールで表示した時に変換している内容が見れます。(React+JSX → React) 個人的にはJSXを使わない方法のほうが好きですし、変換の手間もなくなるのでパフォーマンス的にも良いのですが可読性を重視するなら使うのはありかなと。

Component

サンプルコードではタグを一つ挿入する例でした。結局jQueryでDOMを生成するのと変わらないように思えます。しかし、複雑なインタフェースを持つ要素や複数の要素が絡み合う場合があり、jQueryでappend、appendでは可読性も良くなくメンテナンス性悪いことこの上ないです。そんな時にReactが強さを発揮します。Reactの強みは、MVCの「V」です。DOMの構成に変化がある場合、簡単に変更できるように仕組み化することができます。それを実現する機能の一つとして、Component、各要素をコンポーネント(部品)化することです。個々の要素の変更を容易にし、レンダリング時にツリー構造で要素を追えるようにすることができます。

<script type="text/jsx">
  var CommentBox = React.createClass({
    render : function() {
      return (
        <div className="commentBox">
          <div className="commentList">
            Hello, world! I am a CommentList.
          </div>
          <div className="commentForm">
            Hello, world! I am a CommentForm.
          </div>
        </div>
      );
    }
  });

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

React.createClass()することで、レンダリングした内容をCommentBoxと宣言した変数(XML)に保持します(コンポーネント化)。その変数(XML)を最終的にReact.render()で指定ID間に挿入しています。 しかし、これでもまだCommentBox要素の中身がごちゃごちゃしています。そこで、さらに細分化します。

<script type="text/jsx">
  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>
      )
    }
  });
  var CommentBox = React.createClass({
    render: function() {
      return (
        <div className="commentBox">
          <CommentList />
          <CommentForm />
        </div>
      )
    }
  });

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

CommentList、CommentFormという風にコンポーネント化して、CommentBox内でその2つの要素を読み込みました。最終的なレンダリングは、CommentBoxで変わりません。2つの要素をコンポーネント化したことにより、例えば2つの要素の上下の表示位置を変更しようと思った場合、CommentBoxのコンポーネントを変更するだけで済むことになります。

props

さて、冒頭よりReactはMVCの「V」であるという話をしています。先程のソースコードもそうですが、コンポーネントはVだけを担う作りになっているでしょうか?jQueryにおけるDOM生成は、レイアウトも構造もデータもまとめてできる反面、メンテナンス性に欠けます。Reactの「V」機能をもっと活用するためには、データ部分も分離するべきです。そこで、以下の様なレイアウトのソースコードのデータ部分を切り離してみようと思います。

<div class="commentBox">
  <div class="commentList">
    <div class="comment">
      <h2 class="commentAuthor">Pete Hunt</h2>
      <p>This is one comment</p>
    </div>
    <div class="comment">
      <h2 class="commentAuthor">Jordan Walke</h2>
      <p>This is another comment</p>
    </div>
  </div>
</div>

2つあるclass="comment"の違いは、h2タグとpタグの中のテキストが違うだけでレイアウトは同じです。このclass="comment"要素をコンポーネント化します。そして出来上がったコンポーネントにh2タグとpタグの中のテキストを渡します。

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

コンポーネント化したCommentListでCommentコンポーネントを呼び出します。その際に属性としてauthor、テキスト内容を要素で囲って渡してみます。これを、Comment側で受け取る時に使用するのが、「props」です。コンポーネント間のI/Fを提供します。 Comment要素呼び出し時にセットした属性値authorの値を、「this.props.author」で受け取ります。囲ったテキスト情報は、「this.props.children」で受け取ります。内容を受け取る時のソースコードが下記のようになります。

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

JSON形式でコンポーネントの外に出す

大分すっきりはしてきましたが、まだコンポーネントがデータを持っていることに変わりはありません。Comment要素の属性値、テキスト値として持っていますのでこれをコンポーネントの外に出します。先程のデータをJSON形式で変数に保存します。

var data = [
  {author: "PeteHunt", text: "This is one comment"},
  {author: "JordanWalke", text: "This is another comment"}
];

このデータをコンポーネントツリーのルートから渡していきます。先程説明したコンポーネント間I/F、propsでCommentBox→CommentList→Commentとデータを渡していきます。 まず、React.render()でCommentBoxをレンダリングする際に、data属性にJSONデータを渡します。コンポーネント間のI/Fはpropsですので、CommentBox→CommentListにthis.props.dataでdataを渡します。

/* CommentBox→CommentListへdataを渡す */
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        <CommentList data={this.props.data} />
      </div>
    )
  }
});

/* dataを渡す */
React.render(
  <CommentBox data={data}/>,
  document.getElementById('example')
);

次にCommentList→Commentへdataを渡していくことになるのですが、この部分は繰り返しになります。そこで、map()関数でdata配列を繰り返してCommnet要素にデータをセットしていきます。 それを最終的に、CommnetNode要素として出力します。

/* CommentList→Commentへdataを渡す */
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>
    )
  }
});

最終的なソースコード全文は以下の通りです。

<!DOCTYPE html>
<html>
<head>
  <script src="http://fb.me/react-0.12.2.js"></script>
  <script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
</head>
<body>
  <div id="example"></div>

  <script type="text/jsx">
    var data = [
      {author: "PeteHunt", text: "This is one comment"},
      {author: "JordanWalke", text: "This is another comment"}
    ];
    var Comment = React.createClass({
      render: function() {
        return (
          <div className="comment">
            <h2 className="commentAuthor">
            {this.props.author}
            </h2>
            <p>{this.props.children}</p>
          </div>
        )
      }
    });
    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>
        )
      }
    });
    var CommentBox = React.createClass({
      render: function() {
        return (
          <div className="commentBox">
            <CommentList data={this.props.data} />
          </div>
        )
      }
    });
    /* React + JSX */
    React.render(
      <CommentBox data={data}/>,
      document.getElementById('example')
    );
  </script>
</body>
</html>

まとめ

Facebookチュートリアルに沿って解説してみましたが、いかがでしたでしょうか?Reactに限ったことではないのですが、JavaScriptフレームワークやライブラリは沢山存在します。その中で自分のプロジェクトの内容・規模にあったものを選択しなければいけません。少なくともちょっとした要素の追加であれば、今まで通りjQueryで何も問題はないと思っています。しかし複雑な案件になれば、jQueryでDOMの生成をするのにも限界があります。生成できなくはないでしょうが、結局構造からデータまで全部同じソースコードを追わなければ読み解けません。事後のメンテナンス性を考えると、こういったライブラリを活用してMVCに則ったプログラム設計をすべきかなと思います。 今回はReactの基本的な部分にしか触れていませんが、まだまだ沢山の便利機能を備えています。型宣言の厳密化やイベント・アニメーション操作などReactの公式サイトに丁寧に記載されています(英語ですけど)。私もまだ全部目を通せていないのでもっと深くReactを学ぼうと思います。あー楽しい(^^)

おまけ

今回話した内容は、社内でも共有しました。その時のスライドも公開しますので、文章より見やすいかもです。参考にしてみてください。(^^)