ヘルプ
ご支援ください

仮想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' }}のように、{式}を使用する必要があります。

読み込み中...