最近流行りの React で簡単な UI を作ってみます。 React は仮想 DOM を使っているのが特徴の View のためのライブラリで、 DOM に状態を持ちません。

状態は DOM とは別に管理され、状態が変化するたびに仮想 DOM を構築します。状態の管理が簡単になる一方でパフォーマンスに悪影響がある、と思わせて仮想 DOM 同士の差分を計算し、その差分だけ実際の DOM に反映させるため、むしろ動作は速いようです。

必要な知識

  • bower コマンドの使い方

React のインストール

React は bower で簡単にインストールできます。

$ bower install react

メッセージを表示するだけのコンポーネントを作ってみる

初めは React を使って画面に Hello, World! と表示してみます。 React とそれを使ったプログラムを HTML から読み込みます。 #view は React で作った要素が追加される場所です。

<!DOCTYPE html>
<meta charset="utf-8">
<div id="view"></div>
<script src="bower_components/react/react.js"></script>
<script src="test.js"></script>
var Message = React.createClass({
    render: function() {
        return React.createElement('div', {}, 'Hello, World!');
    }
});

var view = document.querySelector('#view');
React.render(React.createElement(Message, {}), view);

これを保存して Web ブラウザで開くと Hello, World! と表示されるはずです。

React で定義できる要素はコンポーネントと呼ばれます。

React.createClass はコンポーネントを作成する関数です。引数として渡した render は仮想 DOM をレンダリングするメソッドです。

React.createElement は要素名やタグに、後で説明する props を与えて (上の例だと {} ) 仮想 DOM を作成します。

React.render は実際の DOM (上の例だと #view ) に仮想 DOM をレンダリングする関数です。

次は Hello, World! 以外のメッセージに切り替えられるように機能を追加します。

コンポーネントに値を値を渡す

外部から値を渡すには React.createElement の第2引数にオブジェクトを渡せば可能です。

さっきの JavaScript を少しだけ変えてみます。

var Message = React.createClass({
    render: function() {
        var name = this.props.name || 'World';
        return React.createElement('div', {}, 'Hello, ' + name + '!');
    }
});

var view = document.querySelector('#view');
React.render(React.createElement(Message, { name: '太郎' }), view);

これを実行すると Hello, 太郎! よ表示されるはずです。

React.createElement の第2引数に与えた値に render の中で this.props として触っています。

name を変更すればいろんな人にあいさつできます。

もっと UI らしいものを作る

テキストを表示するだけだとアレなので、もっと実際のアプリに近いものを作ってみます。

var Component = React.createClass({
    getInitialState: function() {
        return {
            text: '',
            messages: []
        };
    },
    onTextChanged: function() {
        this.setState({
            text: this.refs.text.getDOMNode().value,
            messages: this.state.messages
        });
    },
    onButtonClicked: function() {
        this.setState({
            text: '',
            messages: this.state.messages.concat([this.state.text])
        });
    },
    render: function() {
        return React.createElement('div', {},
                React.createElement('input', {
                    type: 'text',
                    ref: 'text',
                    value: this.state.text,
                    onChange: this.onTextChanged
                }),
                React.createElement('button', {
                    onClick: this.onButtonClicked
                }, '送信'),
                this.state.messages.map(function(m, i) {
                    return React.createElement('div', { key: i }, m)
                })
            );
    }
});

var view = document.querySelector('#view');
var dom = React.createElement(Component, {});
React.render(dom, view);

実行すると入力欄とボタンが表示されるはずです。ボタンを押すとメッセージが下に増えていくチャットのなりそこないです。

まず新しいのは state です。その名の通りコンポーネントの状態を保持しているプロパティで一番初めに getInitialState が呼ばれてそれがセットされます。 setState で状態は変更でき、状態が変わるたびに render が呼ばれて画面が更新されます。

React.createElement の第2引数 に ref を指定すると this.refs からその要素にアクセスできるようになります。

input 要素を使っていますが、ただ作るだけだと value の更新ができず、 onChange で変更したタイミングで setState する必要があります。少し面倒に思えるかもしれませんが、値の変更が常に1方向になるので結果的には管理が簡単なります。

ここまでやってきて React.createElement がかなりたくさん登場しました。次はこれを簡単に書ける方法を試して見ます。

JSX を試す

JSX で検索すると色々同じ名前のものが出てきて残念な感じですが、 Facebook の言う JSX は JavaScript の中に書くことができる XML のような記法のことです。

当然そのままだと構文エラーなので、 JSXTransformer.js を読み込んだ上で script 要素の typetext/jsx を指定する必要があります。

<!DOCTYPE html>
<meta charset="utf-8">
<div id="view"></div>
<script src="bower_components/react/react.js"></script>
<script src="bower_components/react/JSXTransformer.js"></script>
<script type="text/jsx" src="test.js"></script>

これで JavaScript の中では React.createElement を JSX を使って書くことができます。

var Component = React.createClass({
    getInitialState: function() {
        return {
            text: '',
            messages: []
        };
    },
    onTextChanged: function(value) {
        this.setState({
            text: this.refs.text.getDOMNode().value,
            messages: this.state.messages
        });
    },
    onButtonClicked: function(text) {
        this.setState({
            text: '',
            messages: this.state.messages.concat([this.state.text])
        });
    },
    render: function() {
        return (
            <div>
                <input type="text" ref="text"
                    value={this.state.text}
                    onChange={this.onTextChanged} />
                <button onClick={this.onButtonClicked}>
                    送信
                </button>
                { this.state.messages.map(function(m, i) {
                    return <div key={i}>{m}</div>
                }) }
            </div>
        );
    }
});

var view = document.querySelector('#view');
var dom = <Component />;
React.render(dom, view);

React.createElement だったところに XML 風の記法で書き込めています。ただの糖衣構文なので必ず使う必要はありませんが、簡潔にかけるのは便利そうです。

まとめ

  • React は仮想 DOM を使った View のためのライブラリ
  • React.createClass
  • React.createElement
  • React.render
  • getInitialState
  • setState
  • JSX