素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: twitter: @javascripter にどうぞ。

FacebookのReactで綺麗で高速なDOMのView操作

それほど新しい話題でもないが、FacebookReact | A JavaScript library for building user interfacesというDOMを抽象化したようなView操作のライブラリをリリースした。

Githubが出してるエディタのAtomが実験的にReactを採用したり、Instagramが採用していたり、エディタでReactのシンタックスがサポートされ始めたり、海外では実際に使われはじめていて、実際に使ってみたら思いのほか使いやすかったので、簡単に使い方を書いていこうと思う。

まず、ReactにはJSXという、htmlをJS内で書ける言語がある。オプショナルなものだが、使ったほうがコードが綺麗になるので、この記事では使っていく。下記コードはEdit fiddle - JSFiddleなどでコンパイルなしにそのままテストできるので、適当にコピーしながら確かめると理解が深まる。

/** @jsx React.DOM */

var div = <div>aaa</div>;

@jsxというノーテーションは、トランスパイラが

var div = React.DOM.div(null, 'aaa');

という風に変換するために必要になるimport文で、必ず必要になる魔法のようなもの。 ここで作成したdivは、実際のDOMとは違いバーチャルDOMと呼ばれるもので、

React.renderComponent(div, document.body);

のようにすると、document.bodyに、必要になった時のみdivが構築される。 必要になった時というのは、Reactは管理化にあるDOMの中身を全て把握していて

for (var i = 0; i < 100; i++) {
  React.renderComponent(div, document.body);
}

としても、実際のDOM構築やHTMLのDOM Treeの変更は1度しか行われないということである。 アプリの作成を行ってプロファイリングをすると、DOMの変更がボトルネックになっている場合が多いので、この方式は速度のでるアプリを開発するのに非常に有効である。

Reactでは、React.createClassを使って、カスタムエレメントのようなものを作成し、Viewをコンポネント化できる。

/** @jsx React.DOM */
var App = React.createClass({
    render: function() {
        return <div>aaa</div>;
    }
});

var app = <App />;
React.renderComponent(app, document.body);

このように、独自のカスタムエレメント風のものを生成することができる。基本的に要素はimmutableになっており、同様であるとみなされるものはReact内で再利用されたり、バーチャルDOMのdiffアルゴリズムに基づいて、自動的に再配置される。 よって、要素の変更時に全部一旦消して挿入し直したり、あまり考えずに自然な形で書いても、メモリを大量に消費する心配はだいぶ少なくなる。

カスタム要素には、属性として文字列やオブジェクトを渡すことができ、これはクラス内でthis.propsとしてアクセスできる。これはReactのページのサンプルからとったもの。

/** @jsx React.DOM */

var Hello = React.createClass({
    render: function() {
        return <div>Hello {this.props.name}</div>;
    }
});
 
React.renderComponent(<Hello name="World" />, document.body);

作成時に渡されるpropsは変更されないという想定がある。

ステート管理をしたい場合、別のオブジェクトであるthis.stateが専用に用意されており、getInitialStateメソッドで最初のthis.stateを生成することができ、その後はthis.setStateを利用してステート管理を行うことで、自動的にViewの更新が行われるようになる。ところで、JSXの属性は普通のHTMLの属性と違い、値は任意のJSオブジェクトを取ることができるので、

onClick={ this.onClick }

などで関数を渡してイベント登録を行うことになる。 ステート管理があるコードは

/** @jsx React.DOM */

var Hello = React.createClass({
    getInitialState: function () {
      return { name: "not clicked"  };
    },

    onClick: function () {
      this.setState( {name: "clicked" });
    },

    render: function() {
        return <div onClick={ this.onClick } >{this.state.name}</div>;
    }
});

React.renderComponent(<Hello />, document.body);

このような形になる。外部から後でView内のstateを変更したい場合は(MVCのように設計したい場合、modelからviewを変更できる必要がある)、 一般に、Modelを作成し、それを属性として渡す。ReactにはModelは付属しないので、Backboneや、好きなものを利用する。主動で作成するとこのような形になる。

/** @jsx React.DOM */

var Hello = React.createClass({
    componentWillMount: function () {
      // 本当は複数登録しても変にならないようにイベントとかを使う
      this.props.model.onTextUpdate = function () {
        // setStateを使ってもいいがオブジェクトが重複するのでmodelを使う時は主動でアップデートを指示するのがベスト
        this.forceUpdate();
      }.bind(this);
      
      
    },
    render: function() {
        return <div>{ this.props.model.getText() }</div>;
    }
});


var helloModel = {
  text_: "hi",
  onTextUpdate: function () {},
  setText: function (text) {
    this.text_ = text;
    this.onTextUpdate();
  },
  getText: function () {
    return this.text_;
  }
};

React.renderComponent(<Hello model={helloModel} />, document.body);


setTimeout(function () {
  helloModel.setText("dynamically updated outside the view!");
}, 1000);

基本的にはこれが全てで、componentDidMountメソッドの定義などView挿入時などのフックAPIReact | Working With the Browserにのっている。

一見独自のSyntaxがあったりして利用が難しいように見えるが、実はAPI自体は極めてシンプルで、このシンタックスを利用しなかったとしても、plainなJSからも普通に簡単に使える。競合であるWelcome - Polymerなどはpolyfillとしての性質や未来志向が強く、実際にサイトで使おうとするとライブラリが巨大かつReactに存在するようなバーチャルDOM管理などはあまりないので、今使うとしたらReactだと思う。

ここでカバーされていない部分はReact | Getting Startedを読むと良い。CSSのプロパティなどもHTML属性と同様に変更を保持してくれたり、もう少し機能がある。Polymerと違いscoped styleなどはないが、別に不便だとは思わない。CSSはclassNameを変更してcssファイルでやるのが管理する上で楽だと思うので、自分はあまり使っていない。