Reactjsのチュートリアルを読んで浮かんだ疑問とそれに対する答え


Reactjsのチュートリアルを読んでみて、大雑把なReactjsの使い方を把握したのですが、疑問に思った点がいくつかあったので調べてみました。

前提

筆者のReactjs力がどれくらいかと言うと

だけの状態です。つまりコンセプトは知っているが、細かい話は何も知らなかった状態です。その状態で前述のチュートリアルを読んだ感じです。

疑問

Q. どうやってcomponentの解決をしている?

var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
React.render(
  <CommentBox />,  // この部分
  document.getElementById('content')
);

CommentBox は単なる変数の名前なので、reactjs側からは CommentBox という名前のcomponentがいることは分からないはずなのに、 React.render する時に <CommentBox /> と書けるのは何故だろう?

A. JSX展開後に CommentBox 変数を直接使っている

先ほどのコードをJSXの翻訳機に通すと次のようなコードになります。

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

見れば分かるように CommentBox 変数を直接使っています。チュートリアル中では特にスコープの話は出てきませんが、componentを使う場合はそのクラスが参照できなければいけないようです。

というわけなので、こんな感じでモジュール化しても大丈夫そうです。

define('CommentBox', ['react'], function (React) {
  return React.createClass(/* .. */)
});
require(['react', 'CommentBox'], function (React, CommentBox) {
  React.render(
    <CommentBox />,
    document.getElementById('content')
  );
});

Q. propsとstateの違いは何?

各componentはpropsとstateを持っています。これらは両方共 render() 時に使うのが主な使い方のようだけど、わざわざ2種類用意しているのは何故?

A. propsはimmutable、stateが変更されると再描画される

propsは変更しない(immutable)が、stateはそうではないとのこと。

Reactjsではアプリケーションをcomponentの階層として実装し、全体としては木構造になります。あるcomponentのstateが setState() によって変更されると、Reactjsはそのcomponentにdirtyフラグを立てます。

そして処理が終わった段階でdirtyなcomponentとその子供達を全てrenderします。

こうすることで新しい仮想DOMを構築するコストを極限まで減らしています。また仮想DOMの差分を計算する際も、変更されている可能性があるcomponentに絞って計算すればいいことになります。

Q. propsとstateの使い分けは?

A. いくつかのチェックリストがあるのでそれを参考にする

  1. 親から渡されるものなら、それはおそらくstateではない
  2. 時間とともに変化するものでないなら、それはおそらくstateではない
  3. 他のstateやpropsから計算できるなら、それは確実にstateではない

stateは一般的に親で管理され、それを使って子にpropsが渡されるようです。なので、stateを管理すべきcomponentは

  • そのstateを使って描画される全てのcompnentの共通の親
  • もしくはそれより上のcomponent
    • いいのが無い場合はstateを保持するだけのcomponentを上位階層に作ってもいい

Q. component終了時にメモリリークを防ぐには?

例えば Backbone.View からあるHTML要素を参照していて、その要素がReactjsによって削除された場合、そのviewオブジェクトからの参照を消さないと、メモリリークが発生してしまいます。

HTML要素に直接紐付いているイベントハンドラだったら、 componentWillUnmount 時に頑張れば終了処理を実行できそうですが、単なるJSオブジェクトの場合はどうすればいいの?

A. viewオブジェクトの作成から終了までをcomponentにやらせる

そもそもReactのあずかり知らないところでHTML要素への参照を保持することが間違っているのだろうと思います。つまり Backbone.View を使いたいなら、そのインスタンスは componentDidMount で作成して参照をcomponentにもたせておき、 componentWillUnmount で削除する訳です。

var CommentBox = React.createClass({
  componentDidMound: function () {
    this.view = new Backbone.View({ el: this.refs.foo.getDOMNode() });
  },
  componentWillUnmount: function () {
    this.view.remove();
  },
  render: function() {
    return (
      <div className="commentBox" ref="foo">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});

おわりに

Reactjsは思想・使い方を含めて非常にシンプルで、理解しやすい印象でした。面倒な箇所をJSXに押し付けているので、JSレベルでは、書いてあることとその挙動が把握しやすくなっているのがいいですね。