最初の一歩
まずは簡単にページの見出しを表示してみましょう。プロジェクトにindex.htmlとapp.jsを用意し、index.htmlを次のように記述します(説明の都合上、プロジェクト構成については後回しにするため、ここではCDN(コンテンツデリバリネットワーク)を通じてReactを利用することにします)。
<html>
<head>
<script src="http://fb.me/react-with-addons-0.12.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
</head>
<body>
<div id="app-container"></div>
<script type="text/jsx">
React.render(
<h1>My TODO</h1>,
document.getElementById('app-container')
);
</script>
</body>
</html>
index.htmlをブラウザで開くと無事、タイトルが表示されたでしょうか?ここでしていることは簡単で、React.renderメソッドにより、<h1>My TODO</h1>要素を<div id="app-container"></div>要素内にレンダリングしています。
よく見ると、ここで見慣れない文法が登場しています。React.jsでは「JSX」というDSL(ドメイン固有言語)を使うことにより、スクリプト中にHTML断片を直感的に記述していくことができます。JSXで書かれたスクリプトは、実行前あるいは実行時にJavaScriptにコンパイルされます。上のコードは実際には次のように変換されてから実行されます(オンラインで確認できます)。
React.render(
React.createElement("h1", null, "My TODO"),
document.getElementById('app-container')
);
例では、JSXTransformerによって実行時にコンパイルを行っていますが、当然ながら実行時のパフォーマンスを下げてしまいます。少なくとも本番では、事前にコンパイルするようにしましょう。また、JSXの利用は必須ではありませんが、特に理由がなければ、可読性のために使用することをお勧めします。
インラインスクリプト部分をファイルに切り出す
早速コーディングを始めたいところですが、その前にインラインスクリプト部分をapp.jsファイルに切り出しておきましょう。
- <script type="text/jsx">
- React.render(
- <h1>My TODO</h1>,
- document.getElementById('app-container')
- );
- </script>
+ <script type="text/jsx" src="app.js"></script>
これに伴って、index.htmlを直接ブラウザで開いても動作しなくなりました。JSXTransformerが内部でAjax通信を行っているため、app.jsへのアクセスがクロスドメイン制約に引っかかったのが原因です。ここからは簡易サーバ越しにindex.htmlにアクセスすることにしましょう。手元にPython環境があれば、同ディレクトリでpython -m SimpleHTTPServer 8080コマンドでサーバを立ち上げ、localhost:8080にアクセスします。Node.js環境があれば、http-serverなどのモジュールを同様に扱うことができます。
ダウンロードしたサンプルで
npm installした後,npm startで動作させることができます。
コンポーネントの作成
ここからはいよいよ、React.jsの中心となる概念であるコンポーネントの仕組みを見ていきます。React.jsでは、再利用したいHTML断片をコンポーネントとして定義することができます。また、コンポーネントは他のコンポーネントからも利用することができるため、大掛かりな画面を作る際にはコンポーネントを次々とネストさせて1つのツリーを構築することになります。
サンプルアプリ作成の手順として、まずはトップページを作ることにします(複数ページを構成する方法については次回扱います)。作成するコンポーネントは、以下の3つです。
-
App: アプリ全体を表示する -
TodoList: 未完了のTODOリストを表示する -
Todo: 個々のTODOを表示する
var todos = [{
id: '_1',
name: 'Buy some milk',
done: true
}, {
id: '_2',
name: 'Birthday present to Alice',
done: false
}];
var Todo = React.createClass({
render: function() {
var todo = this.props.todo;
return (<li>{todo.name}<button>Done</button></li>);
}
});
var TodoList = React.createClass({
render: function() {
var rows = this.props.todos.filter(function(todo) {
return !todo.done;
}).map(function(todo) {
return (<Todo key={todo.id} todo={todo}></Todo>);
});
return (
<div className="active-todos">
<h2>Active</h2>
<ul>{rows}</ul>
</div>
);
}
});
var App = React.createClass({
render: function() {
return (
<div>
<h1>My Todo</h1>
<TodoList todos={todos}/>
</div>
);
}
});
React.render(
<App></App>,
document.getElementById('app-container')
);
コンポーネントはReact.createClassによって生成します。<TodoList todos={todos}/>という記述に注目してください。JSXのシンタックスによって、コンポーネントとそれに与えるプロパティは、あたかもHTMLタグとその属性を拡張したように表現されているのが分かると思います。
JSXについては、コードからの推測を期待して説明を省略しています。仕様の詳細やここでは紹介しない文法については公式ドキュメントを参照してください。
コンポーネントの仕事は、renderメソッドで自身に与えられたプロパティを元にHTML断片を生成して返すことです。ここで重要なことは、このrenderメソッドがコンポーネントの描画に関わる唯一のメソッドだということです。何かのイベントをきっかけに再描画する際も、内部のDOMにアクセスして一部を書き換えるのではなく、必ずこのrenderメソッドを使うようにしてください。
ここまでで覚えておくべきいくつかのポイントに触れておきます。
-
自身に与えられたプロパティは、
this.propsから取得することができます。 -
コンポーネントの配列を作る場合には、配列内で一意な
key属性を必ず付与します。key属性は後述するDiffアルゴリズムのためにReactが必要としているものです。 付与し忘れた場合コンソールに警告が表示されるので、厄介なバグを生む前に対応しましょう。 -
class属性、for属性は、例外としてそれぞれclassName、htmlForを使います。 これはJSXのコンパイル後にJavaScriptの予約語と干渉するためです。
イベントの取得と発火
コンポーネント同士を疎結合に保つために、コンポーネントはツリーの中で自身の親や兄弟にあたるコンポーネントにアクセスすることは許されません。では、子供の情報を親に渡すためにはどうすればよいでしょうか? そこでイベントを使います。
イベントの仕組みを知るためにフォームを作りましょう。TodoFormという名前でコンポーネントを作り、TodoListのリストのすぐ下に差し込みます。サンプルが小規模のため、あまり専用コンポーネントにする意義が感じられないかもしれませんが。
var TodoForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
var name = this.refs.todoName.getDOMNode().value.trim();
if(name) {
alert(name);
// this.props.onSubmitTodo(name);// to dispatch event
this.refs.todoName.getDOMNode().value = '';
}
},
render: function() {
return (
<form onSubmit={this.handleSubmit}>
<input ref="todoName"></input><input type="submit"></input>
</form>
);
}
});
例では、TodoFormコンポーネント内でsubmitイベントを取得しています。eはクロスブラウザで動作するEventオブジェクトのラッパーです(イベントの種類と詳細)。ところで、大量のイベントリスナの登録はパフォーマンスを悪化させることが知られています。そこで、React.jsのイベントシステムはルート要素に単一のイベントリスナを登録することで、この問題を回避しています。
また、例ではコメントアウトされていますが、もしこのコンポーネントが新たに独自のイベントを発火する必要がある場合、単にthis.propsに渡されたコールバック関数を呼ぶことにより実現できます。宣言側のコードは次のようになるでしょう。
<TodoForm onSubmitTodo={this.handleSubmit}/>
ところで、React.jsは基本的にDOMを直接触らなくてもよいように設計されていますが、いざというときのため、DOMにアクセスするためのAPIも用意されています。ここではref属性(Reactが提供する便利な仕組みの1つです)を使って、入力値をDOMから受け取っています。
しかし、ここで気になることが2つあります。
-
inputNodeを初期化するためにDOMを直接いじっており、「render以外で描画を行わない」という掟を破っています。 -
handleSubmitメソッドで何をすれば新しいTODOが画面に反映されるでしょうか。
そこで、次節でstate(状態)という概念を扱っていきます。
