コンポーネント
このチュートリアルのパート1で触れたように、仮想DOMアプリケーションの主要な構成要素はコンポーネントです。コンポーネントは、HTML要素と同様に、仮想DOMツリーの一部としてレンダリングできる、アプリケーションの自己完結型の部分です。コンポーネントは関数呼び出しのように考えることができます。どちらもコードの再利用と間接化を可能にするメカニズムです。
説明のために、HTMLの<button>
要素を表す仮想DOMツリーを返す、MyButton
という単純なコンポーネントを作成してみましょう。
function MyButton(props) {
return <button class="my-button">{props.text}</button>
}
JSXでこのコンポーネントを参照することで、アプリケーション内で使用できます。
let vdom = <MyButton text="Click Me!" />
// remember createElement? here's what the line above compiles to:
let vdom = createElement(MyButton, { text: "Click Me!" })
JSXを使用してHTMLのツリーを記述する場所であればどこでも、コンポーネントのツリーも記述できます。違いは、コンポーネントはJSXでは、コンポーネントの名前(JavaScript変数)に対応する大文字で始まる名前を使用して記述されることです。
PreactはJSXによって記述された仮想DOMツリーをレンダリングする際に、遭遇する各コンポーネント関数をツリーのその場所で呼び出します。例として、そのコンポーネントを表すJSX要素をrender()
に渡すことで、MyButton
コンポーネントをWebページの本文にレンダリングできます。
import { render } from 'preact';
render(<MyButton text="Click me!" />, document.body)
コンポーネントのネスト
コンポーネントは、返される仮想DOMツリー内の他のコンポーネントを参照できます。これにより、コンポーネントのツリーが作成されます。
function MediaPlayer() {
return (
<div>
<MyButton text="Play" />
<MyButton text="Stop" />
</div>
)
}
render(<MediaPlayer />, document.body)
この手法を使用して、異なるシナリオに対して異なるコンポーネントのツリーをレンダリングできます。音が再生されていないときは「再生」ボタンを、音が再生されているときは「停止」ボタンを表示するように、MediaPlayer
を作成してみましょう。
function MediaPlayer(props) {
return (
<div>
{props.playing ? (
<MyButton text="Stop" />
) : (
<MyButton text="Play" />
)}
</div>
)
}
render(<MediaPlayer playing={false} />, document.body)
// renders <button>Play</button>
render(<MediaPlayer playing={true} />, document.body)
// renders <button>Stop</button>
注意: JSXの
{中括弧}
を使用すると、プレーンなJavaScriptに戻ることができます。ここでは、三項演算子を使用して、playing
プロパティの値に基づいて異なるボタンを表示しています。
コンポーネントの子
コンポーネントは、HTML要素と同様にネストすることもできます。コンポーネントが強力なプリミティブである理由の1つは、コンポーネント内にネストされた仮想DOM要素のレンダリング方法を制御するためのカスタムロジックを適用できることです。
この仕組みは非常にシンプルです。JSXでコンポーネント内にネストされた仮想DOM要素は、特別なchildren
プロパティとしてそのコンポーネントに渡されます。コンポーネントは、{children}
式を使用してJSXで参照することで、子の配置場所を選択できます。または、コンポーネントは単にchildren
値を返すことができ、Preactはそれらの仮想DOM要素を、そのコンポーネントが仮想DOMツリーに配置された場所にレンダリングします。
<Foo>
<a />
<b />
</Foo>
function Foo(props) {
return props.children // [<a />, <b />]
}
前の例を振り返ると、MyButton
コンポーネントは、表示テキストとして<button>
要素に挿入されるtext
プロパティを予期していました。テキストの代わりに画像を表示したい場合はどうでしょうか?
children
プロパティを使用してネストできるように、MyButton
を書き直してみましょう。
function MyButton(props) {
return <button class="my-button">{props.children}</button>
}
function App() {
return (
<MyButton>
<img src="icon.png" />
Click Me!
</MyButton>
)
}
render(<App />, document.body)
他のコンポーネントをレンダリングするコンポーネントの例をいくつか見てきたので、ネストされたコンポーネントによって、多くの小さな個々の部分から複雑なアプリケーションをどのように組み立てられるかが明らかになってきたと思います。
コンポーネントの種類
これまでは、関数であるコンポーネントを見てきました。関数コンポーネントは入力としてprops
を受け取り、出力として仮想DOMツリーを返します。コンポーネントはJavaScriptクラスとして記述することもできます。これはPreactによってインスタンス化され、関数コンポーネントと同様に動作するrender()
メソッドを提供します。
クラスコンポーネントは、PreactのComponent
基底クラスを拡張することで作成されます。以下の例では、render()
が関数コンポーネントと同様に、入力としてprops
を受け取り、出力として仮想DOMツリーを返すことに注目してください。
import { Component } from 'preact';
class MyButton extends Component {
render(props) {
return <button class="my-button">{props.children}</button>
}
}
render(<MyButton>Click Me!</MyButton>, document.body)
コンポーネントの定義にクラスを使用する理由は、コンポーネントの*ライフサイクル*を追跡するためです。Preactは、仮想DOMツリーのレンダリング時にコンポーネントに遭遇するたびに、クラスの新しいインスタンス(new MyButton()
)を作成します。
ただし、第1章で説明したように、Preactには繰り返し新しい仮想DOMツリーを与えることができます。Preactに新しいツリーを与えるたびに、前のツリーと比較して2つのツリー間の変更が判別され、それらの変更がページに適用されます。
コンポーネントがクラスを使用して定義されている場合、ツリー内のそのコンポーネントに対する*更新*は、同じクラスインスタンスを再利用します。つまり、次回のrender()
メソッドの呼び出し時に使用できるデータを、クラスコンポーネント内に格納することが可能です。
クラスコンポーネントは、Preactが仮想DOMツリーの変更に応じて呼び出す、多数のライフサイクルメソッドを実装することもできます。
class MyButton extends Component {
componentDidMount() {
console.log('Hello from a new <MyButton> component!')
}
componentDidUpdate() {
console.log('A <MyButton> component was updated!')
}
render(props) {
return <button class="my-button">{props.children}</button>
}
}
render(<MyButton>Click Me!</MyButton>, document.body)
// logs: "Hello from a new <MyButton> component!"
render(<MyButton>Click Me!</MyButton>, document.body)
// logs: "A <MyButton> component was updated!"
クラスコンポーネントのライフサイクルにより、props
をツリーに厳密にマッピングするのではなく、変更に応答するアプリケーションの部分を構築するための便利なツールになります。また、仮想DOMツリーに配置されている各場所で個別に情報を格納する方法も提供します。次の章では、コンポーネントが変更したいときにいつでもツリーのセクションを更新する方法について説明します。
試してみましょう!
練習として、コンポーネントについて学んだことと、前の2つの章で学んだイベントスキルを組み合わせてみましょう!
style
、children
、onClick
プロパティを受け取り、それらのプロパティが適用されたHTML <button>
要素を返す、MyButton
コンポーネントを作成します。