仮想DOM
「仮想DOM」という言葉を聞いたことがあるかもしれません。「仮想」とはどういう意味なのか、ブラウザのプログラミングで使用する実際のDOMと「仮想」DOMはどのように違うのか疑問に思ったことがあるかもしれません。
仮想DOMは、オブジェクトを使用したツリー構造の簡単な記述です。
let vdom = {
type: 'p', // a <p> element
props: {
class: 'big', // with class="big"
children: [
'Hello World!' // and the text "Hello World!"
]
}
}
Preactのようなライブラリは、これらの記述を構築する方法を提供し、それらをブラウザのDOMツリーと比較することができます。ツリーの各部分が比較されると、ブラウザのDOMツリーは仮想DOMツリーによって記述された構造と一致するように更新されます。
これは便利なツールです。なぜなら、キーボードやマウスの入力のようなものに応じてDOMを命令的ではなく宣言的に構成できるからです。キーボードやマウスの入力に応じてDOMをどのように更新するかを記述する代わりに、その入力が受信された後、DOMがどのように見えるべきかだけを記述する必要があります。つまり、Preactにツリー構造の記述を繰り返し与えることができ、現在の構造に関係なく、ブラウザのDOMツリーを新しい記述に合わせて更新します。
この章では、仮想DOMツリーを作成する方法と、Preactにそれらのツリーに合わせてDOMを更新するように指示する方法を学びます。
仮想DOMツリーの作成
仮想DOMツリーを作成する方法はいくつかあります。
createElement()
: Preactによって提供される関数- JSX: JavaScriptにコンパイルできるHTMLのような構文
- HTM: JavaScriptで直接記述できるHTMLのような構文
PreactのcreateElement()
関数を直接呼び出すのが最も簡単な方法なので、そこから始めるのが便利です。
import { createElement, render } from 'preact';
let vdom = createElement(
'p', // a <p> element
{ class: 'big' }, // with class="big"
'Hello World!' // and the text "Hello World!"
);
render(vdom, document.body);
上記のコードでは、段落要素の仮想DOM「記述」を作成しています。createElementの最初の引数はHTML要素名です。2番目の引数は要素の「props」で、要素に設定する属性(またはプロパティ)を含むオブジェクトです。追加の引数は要素の子で、文字列('Hello World!'
のような)または追加のcreateElement()
呼び出しからの仮想DOM要素にすることができます。
最後の行では、Preactに仮想DOM「記述」と一致する実際のDOMツリーを構築し、そのDOMツリーをWebページの<body>
に挿入するように指示します。
さらにJSXで!
JSXを使用すると、機能を変更せずに前の例を書き換えることができます。JSXを使用すると、HTMLのような構文を使用して段落要素を記述できるため、より複雑なツリーを記述する際に読みやすくすることができます。JSXの欠点は、コードがJavaScriptで記述されなくなり、Babelのようなツールでコンパイルする必要があることです。コンパイラは、以下のJSXの例を前の例で見た正確なcreateElement()
コードに変換する作業を行います。
import { createElement, render } from 'preact';
let vdom = <p class="big">Hello World!</p>;
render(vdom, document.body);
だいぶHTMLに似てきました!
JSXについて最後に注意すべき点が1つあります。JSX要素(山かっこ内)内のコードは、特別な構文でありJavaScriptではありません。数値や変数のようなJavaScript構文を使用するには、テンプレート内のフィールドと同様に、最初に{式}
を使用してJSXから「ジャンプ」して戻る必要があります。以下の例は、class
をランダム化された文字列に設定する式と、数値を計算する式の2つの式を示しています。
let maybeBig = Math.random() > .5 ? 'big' : 'small';
let vdom = <p class={maybeBig}>Hello {40 + 2}!</p>;
// ^---JS---^ ^--JS--^
render(vdom, document.body)
を実行すると、「Hello 42!」というテキストが表示されます。
もう一度HTMで
HTMは、標準のJavaScriptタグ付きテンプレートを使用するJSXの代替であり、コンパイラが不要になります。タグ付きテンプレートに出会ったことがない場合、それらは${式}
フィールドを含めることができる特別なタイプの文字列リテラルです。
let str = `Quantity: ${40 + 2} units`; // "Quantity: 42 units"
HTMは、JSXの{式}
構文の代わりに${式}
を使用するため、コードのどの部分がHTM/JSX要素で、どの部分がプレーンなJavaScriptであるかを明確にすることができます。
import { html } from 'htm/preact';
let maybeBig = Math.random() > .5 ? 'big' : 'small';
let vdom = html`<p class=${maybeBig}>Hello ${40 + 2}!</p>`;
// ^--JS--^ ^-JS-^
これらの例はすべて同じ結果を生成します。これは、Preactに渡して既存のDOMツリーを作成または更新できる仮想DOMツリーです。
寄り道: コンポーネント
このチュートリアルの後半でコンポーネントについて詳しく説明しますが、今のところ、<p>
のようなHTML要素は、仮想DOM要素の2つのタイプの1つにすぎないことを知っておくことが重要です。もう1つのタイプはコンポーネントで、型がp
のような文字列ではなく関数である仮想DOM要素です。
コンポーネントは、仮想DOMアプリケーションの構成要素です。今のところ、JSXを関数に移動することで、非常にシンプルなコンポーネントを作成します。
import { createElement } from 'preact';
export default function App() {
return (
<p class="big">Hello World!</p>
)
}
render(<App />, document.getElementById("app"));
コンポーネントをrender()
に渡すときは、予期しない方法で壊れるため、コンポーネントを直接呼び出すのではなく、Preactにインスタンス化を実行させることが重要です。
const App = () => <div>foo</div>;
// DON'T: Invoking components directly breaks hooks and update ordering:
render(App(), rootElement); // ERROR
render(App, rootElement); // ERROR
// DO: Passing components using createElement() or JSX allows Preact to render correctly:
render(createElement(App), rootElement); // success
render(<App />, rootElement); // success
試してみましょう!
このページの右側には、以前の例のコードが上部に表示されます。その下には、そのコードを実行した結果が表示されたボックスがあります。コードを編集して、変更が結果にどのように影響するか(または壊れるか)を確認できます。
この章で学んだことをテストするには、テキストをもっと派手にしてみましょう!World
という単語を<em>
と</em>
のHTMLタグで囲んで目立たせてください。
次に、style
プロップを追加して、すべてのテキストを紫にします。style
プロップは特殊で、要素に設定する1つ以上のCSSプロパティを持つオブジェクト値を許可します。オブジェクトをプロップ値として渡すには、style={{ property: 'value' }}
のように、{式}
を使用する必要があります。