bitsofcode
最近、DOM とシャドウ DOM とはいったい何なのか、どう違うのかについて書いています。 要約すると、Document Object Model は HTML ドキュメントをオブジェクトベースで表現したもので、そのオブジェクトを操作するためのインターフェイスです。 シャドウDOMは、DOMの「ライト」バージョンと考えることができます。 これもまた、HTML 要素をオブジェクトベースで表現したものですが、完全なスタンドアロン文書ではありません。 その代わり、シャドウ DOM により、DOM をより小さくカプセル化されたビットに分離し、HTML ドキュメント全体で使用することができます。 この概念は数年前からありましたが、React フレームワークで使用されたことにより、より一般的になりました。 この記事では、仮想 DOM とは何か、元の DOM とどう違うのか、どのように使用されるのかを正確に説明します。
Why do we need a virtual DOM? 前述のように、DOM には HTML ドキュメントのオブジェクト ベースの表現と、そのオブジェクトを操作する API の 2 つの部分があります。
たとえば、順序なしリストと 1 つのリスト項目を持つこの単純な HTML ドキュメントを例に挙げてみましょう。
<!doctype html><html lang="en"> <head></head> <body> <ul class="list"> <li class="list__item">List item</li> </ul> </body></html>
この文書は次の DOM ツリーとして表すことができます。
- html
- head lang=”ja”
- body
- ul class=”list”
- li class=”list__item”
- “List item”
この文書は次のような DOM ツリーで表されます。
- li class=”list__item”
- ul class=”list”
ここで、最初のリスト項目の内容を
"List item one"
に変更し、さらに2番目のリスト項目も追加したいとします。 これを行うには、DOM API を使用して更新したい要素を見つけ、新しい要素を作成し、属性とコンテンツを追加し、最後に DOM 要素自体を更新する必要があります。const listItemOne = document.getElementsByClassName("list__item");listItemOne.textContent = "List item one";const list = document.getElementsByClassName("list");const listItemTwo = document.createElement("li");listItemTwo.classList.add("list__item");listItemTwo.textContent = "List item two";list.appendChild(listItemTwo);
The DOM wasn’t made for this…
DOM に関する最初の仕様が 1998 年に発表されたとき、Web ページの構築と管理は全く異なる方法で行われました。
document.getElementsByClassName()
のような単純なメソッドは小規模に使用するには問題ありませんが、ページ上の複数の要素を数秒ごとに更新していると、DOM を常に照会して更新するのに本当に費用がかかるようになります。さらに、API が設定されているため、通常は、特定の要素を検索して更新するよりも、ドキュメントの大きな部分を更新する、より高価な操作を実行する方が簡単です。 リストの例に戻ると、特定の要素を変更するよりも、順序なしリスト全体を新しいものに置き換える方が簡単な場合があります。
const list = document.getElementsByClassName("list");list.innerHTML = `<li class="list__item">List item one</li><li class="list__item">List item two</li>`;
この特定の例では、メソッド間のパフォーマンス差はおそらく重要ではありません。 しかし、Web ページのサイズが大きくなると、必要なものだけを選択して更新することがより重要になります。
…but the virtual DOM was!
The virtual DOM is created to solve these problems of needing to frequently update the DOM in a more performanceant way. DOM や shadow DOM とは異なり、仮想 DOM は公式な仕様ではなく、DOM と連携する新しい方法です。
仮想 DOM は、オリジナルの DOM のコピーと考えることができます。 このコピーは、DOM API を使用せずに、頻繁に操作され、更新されることがあります。 すべての更新が仮想 DOM に対して行われた後、元の DOM に対してどのような特定の変更を行う必要があるかを調べ、ターゲットとなる最適化された方法でそれを行うことができます。
仮想 DOM はどのように見えるのですか
「仮想 DOM」という名前は、その概念が実際に何であるかについての謎を深める傾向があります。 実際には、仮想 DOM は通常の Javascript オブジェクトにすぎません。
以前に作成した DOM ツリーをもう一度見直してみましょう。
- html
- head lang=”en”
- body
- ul class=”list”
- li class=”list__item”
- “リスト項目”
- li class=”list__item”
このツリーはJavascriptオブジェクトとして表現することも可能である。
const vdom = { tagName: "html", children: } // end ul ] } // end body ]} // end html
このオブジェクトは仮想 DOM と考えることができます。 オリジナルの DOM のように、これは HTML ドキュメントのオブジェクト ベースの表現です。 しかし、これはプレーンな Javascript オブジェクトなので、必要になるまで実際の DOM に触れることなく、自由に、頻繁に操作できます。
オブジェクト全体に対して 1 つのオブジェクトを使用するのではなく、仮想 DOM の小さなセクションで作業することがより一般的になっています。 たとえば、順序なしリスト要素に対応する
list
コンポーネントで作業することがあります。const list = { tagName: "ul", attributes: { "class": "list" }, children: };
Under the hood of the virtual DOM
さて、仮想 DOM がどのように見えるかを見てきましたが、DOM のパフォーマンスとユーザビリティの問題を解決するには、どのように機能すればよいのでしょうか?
先ほど述べたように、仮想 DOM を使用して、DOM に行う必要のある特定の変更を単独で行い、その特定の更新を単独で行うことができるのです。 順序なしリストの例に戻り、DOM API を使用して行ったのと同じ変更を行ってみましょう。
最初に行うことは、行いたい変更を含む仮想 DOM のコピーを作成することです。
const copy = { tagName: "ul", attributes: { "class": "list" }, children: };
この
copy
は、元の仮想 DOM (この場合はlist
) と更新後の DOM の間の「差分」と呼ばれるものを作成するために使用されます。 diff は次のようになります。const diffs =
この diff は、実際の DOM を更新する方法についての指示を提供します。
const domElement = document.getElementsByClassName("list");diffs.forEach((diff) => { const newElement = document.createElement(diff.newNode.tagName); /* Add attributes ... */ if (diff.oldNode) { // If there is an old version, replace it with the new version domElement.replaceChild(diff.newNode, diff.index); } else { // If no old version exists, create a new node domElement.appendChild(diff.newNode); }})
これは、仮想 DOM がどのように機能するかの本当に単純化された、簡略化されたバージョンであり、ここではカバーしなかった多くのケースがあることに注意してください。
The virtual DOM and frameworks
上記の例で示したように、仮想 DOM と直接インターフェイスするよりも、フレームワーク経由で仮想 DOM を使用する方が一般的です。 たとえば、
list
コンポーネントは React で次のように記述できます。import React from 'react';import ReactDOM from 'react-dom';const list = React.createElement("ul", { className: "list" }, React.createElement("li", { className: "list__item" }, "List item"));ReactDOM.render(list, document.body);
リストを更新したい場合、リスト テンプレート全体を書き換えて、新しいリストを渡して
ReactDOM.render()
を再度呼び出すだけでよい。 変更が起こったときに開発者ツールを見ると、変更された特定の要素および要素の特定の部分がわかります。The DOM vs the virtual DOM
要約すると、仮想 DOM は、より簡単でパフォーマンスの高い方法で DOM 要素と連携できるようにするツールです。 これは、DOM の Javascript オブジェクト表現であり、必要なだけ頻繁に変更することができます。 このオブジェクトに加えられた変更は照合され、実際のDOMへの変更はターゲットとされ、より少ない頻度で行われます。
- ul class=”list”