仮想DOMについて

今日、チーム内で仮想DOMについての話になったので再度まとめてみたいと思います。

これまでVueを使ってきたものの仮想DOMについてはあまりちゃんと理解せずに来たので整理できた良い機会でした。

ちゃんと調べる時間を取れずに話したのでこの記事ではもう少し深堀りしたいと思います。

DOMについて

そもそもDOMとは何か、というところから始めたいと思います。

MDNのDOMの紹介によると、

ドキュメントオブジェクトモデル (DOM) はウェブ文書のためのプログラミングインターフェイスです。ページを表現するため、プログラムが文書構造、スタイル、内容を変更することができます。 DOM は文書をノードとオブジェクトで表現します。そうやって、プログラミング言語をページに接続することができます。

とのこと。ノードとオブジェクトから構成されているということがわかりました。

さらにこちらによると、

DOM は文書を論理的なツリーで表現します。ツリーのそれぞれの枝はノードで終わっており、それぞれのノードがオブジェクトを含んでいます。 DOM のメソッドでプログラム的にツリーにアクセスできます。これにより、文書構造やスタイルやコンテンツを変更することができます。

DOMはツリー構造を持っていることがわかります。

例としてHTMLを挙げると文書全体を指すdocumentがあり、その下にhtml、さらにその下にheadbodyがあって、更にその下に...という感じでたくさんの階層から成っています。

このツリー構造を構成する1つ1つの要素はNodeと呼ばれます。

DOM の Node インターフェイスは、他の多くの DOM API オブジェクトのベースとなる抽象的な基底クラスです。したがって、これらのオブジェクト型と類似しており、しばしば交換して使用することができます。抽象クラスであるため、単なる Node オブジェクトというものは存在しません。 Node の機能を実装しているオブジェクトはすべて、何れかのサブクラスに基づいています。最も注目すべきものは、 Document, Element, DocumentFragment です。
引用元:Node - Web API | MDN

Nodeの機能を実装したものとしてElement等があり、各HTMLタグを操作できるHTMLElementはこれを継承したものです。

例えば<div>タグに相当するHTMLDivElementはHTMLElementを継承しています。

DOM操作

従来のJavaScriptではこれらのDOMを直接操作していました。

例えばボタンをクリックすると表示される文字が変わる処理をjQueryを使って書くと、

<div>
  <button id="button">
    click
  </button>
  <p id="text">hogehoge</p>
</div>

<script>
  $('#button').on('click', function () {
    $('#text').html('fugafuga');
  });
</script>

みたいな感じになります(jQueryの読み込みは割愛してます)。

これぐらいシンプルな処理だとこのように直接DOMを操作しても良いのですが、複雑な処理、例えば配列等が絡むとかなり面倒です。

直接DOMを操作するにはid等を都度指定しなければなりません。

またDOMを操作する度にブラウザがDOMツリーを再度評価し、レンダリングが行われます。複雑な処理であればあるほど、レンダリングコストが高いことは想像がつくと思います。

仮想DOM

これに対し仮想DOMでは変更のあった箇所のみ再レンダリングが行われます。

仮想DOMの正体はJavaScriptのオブジェクトです。

DOMツリーを仮想的に構築したオブジェクトを2つ用意して、変更前と変更後の差分のみをリアルDOMに反映する仕組みになっています。

この時、直接リアルDOMに対して値の変更等の処理を行うのではなく仮想DOMに対して行っています。

その為、直接リアルDOMを操作するよりもレンダリングコストは低く抑えられます。

また、実際のビュー部分はリアルDOM、更新処理は仮想DOMに任せることで責務の分離も図れます。

先程の例をVueで書くと、

<template>
  <div>
    <button @click="changeText">
      click
    </button>
    <p>{{ text }}</p>
  </div>
</template>

<script>
export default {
  data() {
    text: 'hogehoge'
  },
  methods: {
    changeText() {
      this.text = 'fugafuga'
    },
  },
}
</script>

このコンポーネントはあくまでも仮想DOMを構築する為のものです。従ってchangeText()も仮想DOMに対しての処理になります。

なのでリアルDOMに対しては基本的には何も操作を行いません。

実際に表示されるのはリアルDOMの方ですからロジックとプレゼンテーションの役割を分離できています。これがMVVMのメリットの1つかと思います。

リアルDOM vs 仮想DOM

とは言え、仮想DOMがいつでも正義かと言うとそうでもないよね、という話にチーム内でもなりました。

例えばフロントでのロジックが無いケース等は仮想DOMを使う必要が無いと思います。

他にも複雑なアニメーション等は直接DOMを操作する方がシンプルで良い場合があります。

上で挙げたようなシンプルな例だと仮想DOMを使うメリットはかなり薄いと思います。

なのでケースバイケースでそれぞれに合ったものを選択するのが良いと思います。

まとめ

以上、仮想DOMについてまとめてみました。

正直まだ調べきれていない部分がかなりあるので間違っているところあればご指摘いただけると幸いです。

チーム内では話が色々膨らんでWeb Component等の話も出たのでそちらもまた調べてまとめてみたいと思います。