複数のコンポーネント
これまでは、データを表示し、ユーザー入力を取り扱う単一のコンポーネントの書き方を見てきました。 次はReactの素晴らしい機能の1つである「Composability(構築可能性)」について学んでいきましょう。
動機付け: 関係性の分離
モジューラー・コンポーネントを構築することで、別の明確に定義されているインターフェースとコンポーネントを再利用することができ、 関数やクラスを使いまわす時と同じような利益を得られることが出来ます。 特にアプリケーションの関連性を分離する一方で、新しいコンポーネントを構築してシンプルにすることを心がけて下さい。 アプリケーションのためにカスタムコンポーネント・ライブラリを構築することで、 ある意味、あなたのドメイン(組織?)に最も適した相応しいUIを表現することが出来るでしょう。(翻訳に自信なし)
構成例
FacebookのGraph APIを使用して、プロフィール画像とユーザー名を表示するシンプルなアバター・コンポーネントを作成してみましょう。
var Avatar = React.createClass({
render: function() {
return (
<div>
<ProfilePic username={this.props.username} />
<ProfileLink username={this.props.username} />
</div>
);
}
});
var ProfilePic = React.createClass({
render: function() {
return (
<img src={'https://graph.facebook.com/' + this.props.username + '/picture'} />
);
}
});
var ProfileLink = React.createClass({
render: function() {
return (
<a href={'https://www.facebook.com/' + this.props.username}>
{this.props.username}
</a>
);
}
});
React.render(
<Avatar username="pwh" />,
document.getElementById('example')
);
所有権
上記の例では、AvatarのインスタンスがProfilePicとProfileLinkのインスタンスを所有します。
Reactでは、他のコンポーネントのpropsを設定するコンポーネントがその所有者になります。
より厳密に言えば、コンポーネントXがコンポーネントYのrender()
メソッドで作成されると、
XはYに所有されていると言えます。
既に述べているように、コンポーネントは自身のpropsを変更出来ないため、 その所有者がそれらpropsに設定したものに対して常に一定になります。 このキー・プロパティが、UIが一定になることが保証されるように導いてくれます。
所有者-被所有者の関係、親-子のそれぞれ関係の違いを描くことが重要になります。
Reactでは所有者-被所有者の関係が特に重要である一方、
親-子の関係はあなたが既によく知っており慣れ親しんだDOMから成るものなのでシンプルです。
この上記の例では、Avatar
がProfilePic
とProfileLink
のインスタンスのdivを所有し、
div
はProfilePic
とProfileLink
のインスタンスの親(ただし、所有者ではない)です。
子コンポーネント
Reactコンポーネントのインスタンスを作成する際に、 下記のように開きタグと閉じタグの間に、追加(補助的?)のReactコンポーネントまたはJavaScript式を含めることが出来ます。
<Parent><Child /></Parent>
親は子のpropを、特別なthis.props.children
にアクセスすることで読み取る事が出来ます。
this.props.children
は分かりにくいデータ構造です。
それらを操作するには、React.Childrenユーティリティを使用して下さい。
子要素の調停
Reactがそれぞれの新しい描画渡し(render pass)を使用して、DOMを更新するプロセスのことを、調停(Reconciliation)と言います。(翻訳に自信なし) 一般的に、子は描画された順に沿って調停が行われます。 例えば、2つの描画渡しが下記のそれぞれのマークアップを生成するとします。
// Render Pass 1
<Card>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
// Render Pass 2
<Card>
<p>Paragraph 2</p>
</Card>
直感的に、<p>Paragraph 1</p>が削除されたと思われるでしょうが、 そうではなく、Reactは1つ目の子要素のテキスト・コンテンツを変更し、最後の要素を削除することでそのDOMの調停を行います。 Reactは子の順番に沿って調停を行います。
ステートフルな子
ほとんどのコンポーネントで、これが問題になることはありません。
ただし、描画渡しを跨いでthis.state
にデータを保持するステートフルなコンポーネントでは、
非常に難しい問題になるケースがあります。
ほとんどのケースで、それらを削除する代わりに、要素を隠す事によって回避することができます。
// Render Pass 1
<Card>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
// Render Pass 2
<Card>
<p style={{display: 'none'}}>Paragraph 1</p>
<p>Paragraph 2</p>
</Card>
動的な子要素
子要素があちこちに移動される(検索結果のような)場合や、
リストの先頭に新しいコンポーネントが追加される(ストリームのような)場合、
状況がより複雑になります。
こういったケースでは、描画渡しを超えて各子要素のIDとstateは保持されるべきであり、
各子要素に対してkey
を割り当てることで識別出来るようにします。
render: function() {
var results = this.props.results;
return (
<ol>
{results.map(function(result) {
return <li key={result.id}>{result.text}</li>;
})}
</ol>
);
}
Reactがkeyが割り当てられた子要素を調停する際に、 その子要素は再度並び替えられるか(上書きされる代わりに)、削除される(再利用される代わりに)ことが約束されます。
key
は、配列内の各コンポーネントのHTML子要素のコンテナ(入れ物)にではなく、
常に配列内のコンポーネントへ直接提供されるべきです。
// WRONG!
var ListItemWrapper = React.createClass({
render: function() {
return <li key={this.props.data.id}>{this.props.data.text}</li>;
}
});
var MyComponent = React.createClass({
render: function() {
return (
<ul>
{this.props.results.map(function(result) {
return <ListItemWrapper data={result}/>;
})}
</ul>
);
}
});
// Correct :)
var ListItemWrapper = React.createClass({
render: function() {
return <li>{this.props.data.text}</li>;
}
});
var MyComponent = React.createClass({
render: function() {
return (
<ul>
{this.props.results.map(function(result) {
return <ListItemWrapper key={result.id} data={result}/>;
})}
</ul>
);
}
});
ReactFragmentオブジェクトを渡すことによって、子要素にkeyを割り当てることも可能です。 詳細については、フラグメントへのkey割り当てを参照して下さい。
データフロー
Reactでは、前述したようにデータはprops
を通じて所有者から所有するコンポーネントへ流れます。
これは一方向データ・バインディングでは効果的であり、
所有者は所有するコンポーネントのprops
に、
所有者がprops
またはstate
を基に処理した何らかの値を紐付け(bind)します。
この処理は再帰的に発生するため、データはこれらが使用されている場所であれば自動的に反映されます。
パフォーマンス上の注意
あなたは、もし所有者の階層下に膨大な数のノードが存在した場合、データを変更すると処理が重くなるのでは、と考えるかもしれません。
幸いなことに、JavaScriptは処理が速く、render()
メソッドは極力シンプルに作られているため、
ほとんどのアプリケーションでこれは非常に速く処理されます。
加えて、ほとんどのボトルネックはJavaScriptの実行では無くDOM変更にあり、
Reactは変更箇所の検知と一括処理をすることで、これを最適化します。
それでも、時にはパフォーマンスに対して、あなたがきめ細かく制御したいと考える事があるでしょう。
このようなケースでは、サブツリーの処理をReactにスキップさせたい際に、
単にshouldComponentUpdate()
を上書きしてfalseを返します。
詳細については、リファレンスを参照して下さい。
注意:
データが実際に変更された際にshouldComponentUpdate()
がfalseを返すと、
Reactは同期によるUI保持を行うことが出来ません。
これを使用している間にどのような処理が行われているかを正しく把握するようにし、
深刻なパフォーマンスの問題を抱えている場合にのみ、この関数を使用して下さい。
DOMに関係するJavaScriptの処理速度を見くびらないように注意して下さい。