FacebookのReactで綺麗で高速なDOMのView操作
それほど新しい話題でもないが、FacebookがReact | 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挿入時などのフックAPIはReact | 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ファイルでやるのが管理する上で楽だと思うので、自分はあまり使っていない。