はじめに
FW = フレームワーク
LIB = ライブラリ
Virtual DOM (仮装DOM) とは?
DOM とは実は直接、関係がありません。
なので、勘違いしてしまいそうな名前ですが、DOM を置き換えるもの では ありません。
端的に表現すると、HTML のサーバーサイド・レンダリング(SSR)のしくみをクライアントサイドにもってきて、HTMLの再構築前後の差分を抽出し DOM APIを用いてDOMに適用するようにした仕組み、となります。
仮想DOM APIを用いて、仮想DOMツリー(HTML片)を組み立て、その仮想DOMツリーの中身が更新された場合、更新前後の仮想DOMツリーを比較、差分を抽出し、DOM API を用いて、DOM に差分(変更)を反映します。
仮想DOM は、DOMを補完する技術です。
仮想DOM では、DOM と同じように HTML の構造をツリー構造として表現しています。
DOM の更新タイミング
仮想DOM ベースFW・LIBの多くでは、任意のタイミングで用意された差分抽出・適用の関数を呼び出して、DOM への差分適用を行うことはできません。
Vue.jsでも、DOM の更新は フレームワークにより管理され自動でバッチ更新されるようになっています、手動で行うことはできません。
仮想DOMベースFW・LIBは、仮想DOMツリー差分のみの DOM への適用により、ブラウザでのUIレンダリングが走る頻度を少なくしています。
Vue.js (や React)では、仮想DOMツリーの構築(仮想DOMツリー)→ステートの更新に伴う仮想DOMの更新(新仮想DOMツリー)→新/旧仮想DOMツリーの比較による差分の抽出→DOMへの反映の処理 をカプセル化し隠蔽してくれているので、ユーザーが意識する必要は(基本的には)ありません。
パフォーマンス上etcの理由から、差分がDOMに反映されるタイミングを意識しておく or 差分がDOMに反映されるタイミングを(間接的に)コントロールする、ことも場合によっては必要になります。
Vue.jsも(Reactも)画面の再描画の(差分がDOMに反映される)タイミングをユーザーがある程度(間接的に)コントロールできるようになっています。
仮想DOMが有効なケースとは?
HTMLがリアルタイムで変化し、UIレンダリングの発生頻度が問題になるアプリ、で、主にリアルタイムで画面が変化するゲームアプリ etcで、威力を発揮する気がします。
と、以前書きましたが、現在は、ブラウザのDOM→UIレンダリングまわりの改良により、仮想DOMを利用することで発生する新旧仮想DOMツリー間の差分計算→DOMへの反映がパフォーマンス上のネックになるようになっていて、リアルタイムで画面が変化するゲームアプリetcの開発には、仮想DOMは向いていません。
UIをコンポーネントとして分割して開発し、UIレンダリングのパフォーマンスが重視されないアプリに向いています。
仮想DOMベースFW・LIBの課題?
仮想DOMでは、仮想DOMツリー(HTML(片))の構築をDOMとは別に行い、仮想DOMツリーの更新前後の差分の抽出を行うので、差分まわりのアルゴリズム (特に差分をDOMに反映するタイミングetcを制御するアルゴリズム)が優秀でないとダメな気がします。
差分まわりのアルゴリズムは改良されてきていますが、JSで実装されているので、パフォーマンス面では頭打ちになりつつある感じです。
Rust etcで仮想DOMまわり(の一部)を実装、WebAssemblyにコンパイル、JSから呼び出す方向に向かう可能性もあります。
JS と WebAssembly 間のやりとりには、パフォーマンス上のオーバーヘッドが存在するので、仮想DOMまわりを WebAssemblyでの実装に置き換えたとしても、パフォーマンスが向上するとは限りません。
パフォーマンス上のネックかつWebAssembly化によりパフォーマンスが向上する箇所がWebAssembly化されていくのかもしれません。
サーバーサイドでは、仮想DOMまわり(の一部)を Rust etcでNode.js/Deno/Bun で利用可能なネイティブモジュールとして実装し、JSで呼び出す方向も考えられますが、クライアントサイドとサーバーサイドで異なる実装となるので、この方向に進むことはないと考えられます、クライアントサイドを棄てるなら別ですが。
仮想DOM自体、ブラウザのUIレンダリングまわりの実装にパフォーマンス上の問題があったことで産まれた技術なので、パフォーマンス上の優位性がなくなれば、廃れ消えていく、と考えられます。
仮想DOMベースFW・LIBの欠点
コンポーネントの再利用に問題があります。
同一フレームワーク内で再利用する場合は問題ないのですが、異なるフレームワーク間でコンポーネントを再利用しようとすると、色々と問題があります。
仮想DOMベースの場合、仮想DOM(API)実装を含むランタイムまわり一式をコンポーネントと共にロードする必要があります。
仮想DOMベースの別FW・LIBを併用する場合、含まれている仮想DOM(API)実装は、共通の部品ではなく、それぞれの固有の実装となっているので、 仮想DOMベースのフレームワークから仮想DOMベースの別FW・LIBのコンポーネントを再利用しようとすると、仮想DOM(API)実装を含むランタイム全体がダブります。
仮想DOMについては、現状の高レベル・レイヤでの(特定のFW・LIBとタイトに結合した)実装ではなく、より低レベル・レイヤでの実装が出てきて、FW・LIB内部で共通に利用されるようにでもならない限り、一般化してはいかないような気がしますと、以前、書いていました。
パフォーマンス上の問題 etcにより仮想DOM(API)実装の汎用化によるFW・LIB内部での共通利用の方向には進みませんでした。
異なるライブラリ/フレームワーク間で再利用できるコンポーネントの開発を行う場合、仮想DOMベースのFW・LIBは採用対象外となり、Lit / Svelte etc のDOMベースFW・LIBが採用されることが多いです。
UIコンポーネント・ライブラリでは、Vue.js / React etc用 が それぞれ開発されていたり、どちらかにしか対応していなかったりします。
Vue.js/React 両対応の場合、DOMベースFW・LIBで開発したコンポーネントをWeb Componentsとしたり、更にラッピングしてVue.js / React それぞれ用のコンポーネントにしたり、しています。
仮想DOMベースFW・LIBの存在は、コンポーネントの自由な再利用の観点では障害にしかなっていません。
DOMベースLIB・FWの復権
Vue.js / React より後発である Svelte / SolidJS では、クライアントサイドでは仮想DOMを利用しないDOMのみの実装で、仮想DOMを利用したフレームワークよりも上の性能を実現しています。
Svelte / SolidJS は、CSR/SSRの双方に対応し、サーバーサイドでは、HTMLテンプレート(JS/TS含む)を(HTML/CSSを生成する)効率的な文字列付加コードにコンパイルする造りになっています。
Svelte / SolidJS は、これらをベースとするフルスタックな?フレームワーク SvelteKit / SolidStart は存在するものの、周辺のライブラリetcは整備中であるため、将来的に Vue.js / React にとって代わるかは未知数です。
仮想DOM ベースFW・LIBのこれから
仮想DOMは、ブラウザのUIレンダリングまわりの実装が熟れておらず、DOM APIを呼び出すと頻繁にUIの再描画が行われることにより発生していたパフォーマンス上の問題を、UIの再描画と切り離して、HTML(片)の更新を行い、変更のあった箇所(HTML(片)の更新差分)のみをDOM に反映することにより、UIの再描画の頻度を減らすことで回避し、ブラウザでのUIのパフォーマンスを引き上げていました。
近年、ブラウザのUIレンダリングまわりの実装の改善により、DOM(API)呼び出し時のUIの再描画のパフォーマンスが改善されてきたため、ネイティブで実装されている DOM ではなくJavaScript で実装された 仮想DOM を利用する根拠となっていたパフォーマンス上の優位性はなくなっています。
クライアントサイドで、仮想DOMベース(WebAssembly化も含めて?)よりDOMベースのほうがパフォーマンス面で有利なら、クライアントサイドでは仮想DOMを棄ててDOMベースの実装に回帰していく可能性が高いように思います。
サーバーサイドについても、SvelteやSolidJSはSSRではHTMLテンプレート(JS/TS含む)を(HTML/CSSを生成する)効率的な文字列操作コードにコンパイルする造りになっているので、仮想DOMベースの実装より仮想DOMを利用しない実装のほうがパフォーマンス上、明確に優位で後方互換性の維持が可能なら、仮想DOMベースFW・LIBの多くは仮想DOMを棄てる方向に向かう可能性が高いでしょう。
仮想DOMベースFW・LIB の多くは、近視眼的には、ハイブリッドな(仮想DOM がパフォーマンスで有利なケースでは 仮想DOM を利用、DOM がパフォーマンスで有利なケースでは DOM のみ利用する) or 脱仮想DOMな(DOMのみ利用する)アーキテクチャに移行していく、と考えられます。
React は、DOMのみの実装→仮想DOMとの併用への移行が行われた Vue.js とは異なり、最初から仮想DOMベースであり、仮想DOM ありきの 仮想DOMと密結合した(仮想DOMを前提とした)アーキテクチャ(設計/構造)となっているので、ハイブリッドなor脱仮想DOMなアーキテクチャに移行するには、アーキテクチャの再設計が必要であり、難しすぎて移行しない可能性があります、移行するにしても時間がかかるのではないか、と思われます。
React が ハイブリッドな or 脱仮想DOMな アーキテクチャに移行するケースでは、ユーザーにとっての破壊的な変更を伴う可能性が高いです。
サーバー・コンポーネント
サーバーサイドでは、クライアント・サイドとは異なり、DOMより仮想DOMのほうが軽量であり、パフォーマンス上のネックとなりえる新旧仮想DOM間の差分計算や差分のDOMへの反映の処理が不要であるため、また、(サーバーサイドでは)パフォーマンス上、DOMより仮想DOMのほうが有利であるため、仮想DOMベースフレームワークでは、サーバーサイドでコンポーネントの組み立てを行い、クライアントサイドでは描画のみを行う、サーバー・コンポーネントを仕組みとして、最近、導入しています。
サーバー・コンポーネント、Vue.js(本体)では、サポートされていませんが、Vue.jsベースのフレームワークであるNuxt(v3以降)ではサポートされています。
Reactでは、RSC(React Server Component) として仕様策定され、React本体とReactベースのフレームワークでサポートされつつあります(Next.jsのみサポート済み、TanStack Start/React Router v7 では未サポート、サポートに向け開発中)。
クライアントサイドとサーバーサイドは、WEBアプリにおいて、車の両輪です。
仮想DOMにパフォーマンス上のネックのないサーバーサイドに逃げるのではなく、クライアントサイドでの性能向上に取り組んでいかないと、仮想DOMベースFW・LIBの多くは、今後、消えていく可能性が高いです。
Vue.js
Vue.jsは、元々、DOMベースのフレームワークであり、仮想DOMは採用されていませんでした。
以前、「仮想DOM に対する対応を行う予定はない」と 作者であるEvan You 氏が、Issue にて明言し、「仮想DOM対応すると、別物になってしまう」とも書かれていました。
ので、DOM APIと同じレベルのレイヤーで DOM APIとの親和性の高い仮想DOM(API)ライブラリが出てくれば対応が行われるかもしれません、と書いていました。
軽量な低レベル仮想DOM(API)実装 snabbdom がリリースされたことで状況が変化し、snabbdom の採用により内部的には 仮想DOM を採用しつつも、利用者は 仮想DOM を意識しなくても利用できる形で実装されたVue.js2.0 の開発が進められ、リリースされました。
Vue.js は 2.0.0 以降、2.xでは、snabbdom をフォークしてカスタマイズした 仮想DOM(API)実装を内蔵しています。v3.0.0以降、 フル・スクラッチで?書き直した 仮想DOM実装を内蔵しているようです。
DOMのみの Vue.js 1.x までは、 DOM 処理のまとめてバッチ適用により、ブラウザでの UI レンダリングの走る頻度を少なくしていました、仮想DOMベースとなったVue.js 2.x以降は、他の仮想DOMベースのフレームワーク/ライブラリと同様に差分のみの DOM への適用により、ブラウザでのUIレンダリングが走る頻度を少なくしています。
Vue.js の DOM → DOM + 仮想DOM へのスイッチ時点では、DOMのみの実装 より DOM + 仮想DOM 併用の実装のほうがクライアントサイドにおいて性能面で有利でした。
Vue.js は、DOMベース → DOM+仮想DOM の移行を行った後、バージョンアップを経ながら、内部実装を洗練してきています。
Vue.js3では、(View)テンプレートのコンパイル時にリアクティビティによる変更のある箇所にマーキング?し、新旧仮想DOMの比較による差分検出時にリアクティビティによる変更のない箇所をスキップすることにより、(仮想DOMを利用した)差分検出のパフォーマンスを向上させています。
仮想DOMありきで設計されたフレームワークではないことが他の仮想DOMベースFW・LIBとの大きな違いです。
Vapor Mode
仮想DOMを利用しない、(Vue.jsのランタイム利用、クライアントサイドではDOMベースの)JavaScriptコードを生成する、テンプレート・コンパイラのSFC(Single File Component)用新モード、Vapor Mode が、v3.6での実験的機能?としてのリリースに向けて、開発中です。
Vapor Modeのベータ版を含むVue.js3.6(beta)、今年(2025)の年末までにリリースされる予定です。
2025/12/23、Vapor Modeのベータ版?を含むVue.js 3.6.0-beta.1がリリースされました。
2026/01/23 3.6.0-beta.4
2026/01/30 3.6.0-beta.5
2026/02/12 3.6.0-beta.6
2026/02/27 3.6.0-beta.7
2026/03/16 3.6.0-beta.8
2026/03/26 3.6.0-beta.9
2026/04/13 3.6.0-beta.10
2026/05/07 3.6.0-beta.11
2026/05/15 3.6.0-beta.12
2026/05/28 3.6.0-beta.13
2026/06/05 3.6.0-beta.14
2026/06/11 3.6.0-beta.15
2027/06/17 3.6.0-beta.16
2026/06/24 3.6.0-beta.17
2026/07/18 3.6.0-rc.1
2026/07/22 3.6.0-rc.2
2026/08/11 3.6.0-rc.3
2026/08/14 3.6.0-rc.4
2026/08/21 3.6.0-rc.5
2026/08/28 3.6.0-rc.6
2026/09/04 3.6.0-rc.7
2026/09/11 3.6.0-rc.8
2026/09/18 3.6.0-rc.9
2026/09/30 3.6.0-rc.10
2026/10/10 3.6.0-rc.11
2026/10/16 3.6.0-rc.12
2026/10/23 3.6.0-rc.13
2026/10/30 3.6.0-rc.14
Vapor Mode は、CSR/SSR の双方に対応し、仮想DOMを利用しないコードを生成します。
Vapor Mode では、Vue.jsランタイムの利用範囲が縮小され、より少ないメモリを使用する、パフォーマンス重視のJavaScriptコードが出力されます。
Vapor Component (Vapor Mode利用)と
Vapor-less Component(Vapor Mode非利用)は混在可能です。
Vapor Mode は、<script setup> + Composition API で記述されたコンポーネント(SFC)のみ、サポートします。
仮想DOM を利用している(内部の)コードも、ブラウザのUIレンダリングまわりetcの実装の改善に伴い、今後も改善されていくことが予想されます。
Vue.js は、パフォーマンスでの優位性から、DOM のみ利用から 仮想DOM との併用にシフトしているので、仮想DOMの利点がなくなれば、仮想DOM との併用から 仮想DOMを利用しない実装(クライアントサイドではDOM利用)にシフトしていくと思われます。
Vapor Mode は、短期的には、オプショナルな機能としてリリースされます。数年?掛けて完成度を高めながら、中〜長期的には<script setup>のSFCでは現行の仮想DOMベースの実装を置き換えていく方向に向かう、と考えられます。
Vapor Modeは <script setup>のSFC以外のコンポーネント(& Options API)をサポートしないため、Vapor Modeが主流になったとしても、現行の仮想DOMベースの実装は<script setup>のSFC以外のコンポーネント(& Options API)のサポートのため(当分は)残り続けるはずです。
Vue.jsでは、仮想DOM と DOM の違いは、内部で吸収されている、ので、ユーザーが意識する必要はありません。
推敲中...