そろそろ、悪ふざけをするブログのネタがつきてきました、山本です。

さて、今回やってみるのは「React.js」です。

なんでも、MVCのVを担当するとか、フレームワークではないとか。

まあ、今更とお考えの方もおられることでしょう。
が、私は枯れたり、普及し始めたり等、導入コストが低くなってから参入する主義ですので今回のようなきっかけが無ければ、未だ触ることはなかったのかと思います。
つまりは、よい機会だったということですね。

さて、今回の方針ですが

  • どういうものなのか。
  • とりあえず、使ってみての感想。

あたりを考えています。

あとは、jQueryで全て頑張るというものにも限界を感じていたりもするので、その辺を解消出来るといいな~といったところです。

どういうものなのか

まず、なんなのかですね。
ざっくりでもいいので、その辺は把握しておきたいです。

何から調べていいのか、分からず色々記事などを探していたところ「Virtual DOM」だと、誰かが言ってました。

これを操作するんだそうです。

「Virtual DOM」って何?

調べる度に、知らない単語や概念が出てくるのは、一番心が折られますね。
まあ、これ会社のブログなんで、心が折れましたで終わらせることは難しいのですが。。。。

なので、我慢して調べていきましょう。

「Virtual DOM」でググッて一番上にある記事を拝見しました。
http://qiita.com/mizchi/items/4d25bc26def1719d52e6

う~ん、よく分からん。

そもそも、通常のDOMと何が異なるのでしょうか?

通常のDOMの操作、説明を掻い摘んでみるとDOMを細かな単位に分けて管理する。
そして、分けたそれぞれを操作するといったところでしょうか?

<ul>
  <li>0001</li>
  <li>0002</li>
  <li>0003</li>
  <li>0004</li>
</ul>

のようなものがあった場合、ulが細かな単位で、liそれぞれもまた細かな単位となると。

その前提で、一番上のliを、例えばjQueryで操作するとすれば

$('li').eq(0).text('foobar');

のような形で操作出来ますね。

これは、データバインディング方式なんだそうです。

で、Virtual DOMは、実際のDOMと対になる仮想DOMを生成する。
ユーザは、仮想DOMを操作して、操作後差分を実際のDOMへと反映する。

分からなくはないのですが、想像力が足りないのか、それほど利便性を感じなかったりしますね。

……、全く利便性を感じないと進めて行くのが難しくなるので、便利そうなことを想像してみましょう。

とある案件で、Backbone.jsで作られた管理画面の改修を行ったことがあります。

それは、一覧画面がいくつもあり、表示する内容はAPIで取得していました。

初期表示の時は良いのですが、データを更新した時など、差分だけを差し替えといった事が諸々の事情から難しい為、毎回DOMを洗い替えをしていました。

勿論これでも動くのですが、洗い替えなので、一瞬リストが消えて、更新されたリストが表示されるといった美しくない挙動であったり。
そもそも、無駄だな~とも思っていました。

なるほど、この辺の動きが仮想DOMであれば、Reactが代わりに差分のみを反映と、よりスマートに書けそうですね。

書いてみる

使ってみたら、もっと有効性に気が付くかもしれません。

とりあえず、簡単に使えるようにしてみます。

まず、この2つのスクリプトを読み込む必要があるようです。

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

reactは分かりますが、JSXTransformerとはなんでしょう。

JSXとは、Facebookが開発した独自規格のようです。
jsの記述を、xml形式のような分かりやすい記述で出来るようになるそうです。

説明だけでは、伝わらないので比較してみます。

  • JS
render: function(){

 return (
    React.createElement('div', {className: 'hoge'}, 'foobar')
  );

}
  • JSX
render: function(){
  return (
    <div className="hoge">foobar</div>
  );

}

分かりやすくなったのか?

<!DOCTYPE html>
<html>
  <head>
    <script src="http://fb.me/react-0.13.3.js"></script>
    <script src="http://fb.me/JSXTransformer-0.13.3.js"></script>
  </head>
  <body>
    <div id="container"></div>
    <script>
      var foobar = React.createClass({
        render: function(){
          return (
            <div className="hoge">foobar</div>
          );

        }
      });
      React.render(<foobar />, document.getElementById('container'));
    </script>
  </body>
</html>

で、実際に動くように書いてみると

SyntaxError: expected expression, got ‘<'

foobar

SyntaxErrorと言われても、馴染みないものなので正しい構文が分かりません。

色々調べるとこの記述が足りなかったようです。

<script type="text/jsx">

jsxを使用する際は、typeの指定が必須のようでした。

アトリビュートを厳密に見るというのは、どうも馴染まないですね。

さて、記述をを追加して再度表示してみたのですが。。。

エラーは確かに出なくなりましたが、何も表示されません。

原因の特定に困ったのですが、どうも変数名が良くなかったようです。

<!DOCTYPE html>
<html>
  <head>
    <script src="http://fb.me/react-0.13.3.js"></script>
    <script src="http://fb.me/JSXTransformer-0.13.3.js"></script>
  </head>
  <body>
    <div id="container"></div>
    <script type="text/jsx">
      var Foobar = React.createClass({
        render: function(){
          return (
            <div className="hoge">foobar</div>
          );

        }
      });
      React.render(<Foobar />, document.getElementById('container'));
    </script>
  </body>
</html>

動くソースに変えてみました。
違いが分かるでしょうか?

foobar が Foobar になっています。
変数は、大文字から始めろってことのようです。

何のルールなんですかね?

何か作ってみる

今回はここで終わりとしたいところなんですが、流石に尺が短いかと思いますので、何か作ってみましょう。

何をと考えたんですが、最近仕事で可変のフォームを作りました。
と、いっても複雑なものではなくjQueryでさらっと作れる簡単なものです。

テキストのフォームがあって、ADDボタンでフォームが増えるという仕様です。

それを、Reactで作ってみましょう。

<!DOCTYPE html>
<html>
  <head>
    <script src="http://fb.me/react-0.13.3.js"></script>
    <script src="http://fb.me/JSXTransformer-0.13.3.js"></script>
  </head>
  <body>
    <div id="container"></div>
    <script type="text/jsx">
      var EventTest = React.createClass({
        values : {},
        changeEvent: function(event){
          this.values[event.dispatchMarker] = event.target.value;
        },
        clickAddEvent: function(event){
          this.setState({
            values : this.values
          });
        },
        renderAddButton: function(key){
          var keys = Object.keys(this.values);
          return !keys.length || keys[keys.length - 1] == key ? (<button onClick={this.clickAddEvent}>ADD</button>) : '';
        },
        renderBlock: function(key){
          return (
            <div>
              <input type="text" onChange={this.changeEvent} />
              {this.renderAddButton(key)}
            </div>
          );
        },
        renderBlocks: function(){
          var result = [this.renderBlock()];
          for (var i in this.values){
            result.push(this.renderBlock(i));
          }
          return result;
        },
        render: function(){
          return (
            <div>
              {this.renderBlocks()}
            </div>
          );

        }
      });
      React.render(<EventTest />, document.getElementById('container'));
    </script>
  </body>
</html>

やっていることのわりに、長いですね。。。

まとめ

気付いた点

  • state

    前述しましたが、仮想DOMなので、実際のDOMに差分を反映する必要があります。
    そこで問題となるのは反映されるタイミングだと思います。

    実際にやってみて分かったことは、そのタイミングは値が変更された時ということです。

    値と一言でいっても、どの値かですがReactではPropとStateの2つの値を保持するようです。
    Propは最初にタグのアトリビュートで指定されたもの、つまり固定の値です。
    Stateは変更される値を格納するものとのことです。
    なので、Stateが変更されたタイミングで差分の反映がされています。

    以下の記述で、反映処理が走ります。

          this.setState({
            values : this.values
          });
  • 値の取得

    普段であれば、値の取得に困ることはないのですが、Reactではどのように取るべきなのかがよく分かりませんでした。
    (最近、jQueryしか使っていないため本来のJSでの操作を忘れているせいかもしれませんが………)

    なので、changeEventで値が変更される度に、その値を変数に格納してごまかしました。
    ただ、値を変数に格納しておくというのは、DOMを主とはしない仮想DOMぽい考え方であると思いますので、間違いではないのかもしれません。

困った点

  • 組み立て方
    そもそもいつもの組み方とは考え方が異なるため、どのようにすべきなのかよく分かりませんでした。

  • 値の設定
    inputに対してValueで値を設定したかったのですが、一度valueを与えると値を変更することが出来なくなりました。
    どうやら、値の変更を試みると、変更直後に差分の反映が走り、元の値に戻されるようでした。
    なので、今回はValueの設定を行っていません。

    これについての、対応方法はchangeEventの度に内部の値を更新する方法でしょうか。
    そうだとすると、この辺は面倒な印象です。

  • jsx
    恐らく()で括った箇所がjsxとして認識され、パースされるのだと思います。
    その為、括った中で分岐を行うことが出来ませんでした。

    もしかすると、その辺の制御を考えた場合、jsxではなくそのままのjsを書いたほうが、柔軟に対処出来るではと思いました。

さて次回ですが、アニメーションを考えています。

DOMを作りなおすReactの仕様ですと、アニメーションと相性が悪そうな印象ですが、やるだけやってみようと思います。

では、また次回。