React、Vue、Angular、Svelte はいずれも「状態に応じて UI を宣言する」ための道具ですが、どの変更をいつ DOM へ反映するかの実現方法は同じではありません。仮想 DOM を採用するかだけで優劣を決めるのではなく、更新の単位、コンパイル時に分かる情報、外部 DOM API との境界を理解する方が実務では役に立ちます。
共通する責務: DOM を手続きから守る
どの方式でも、アプリケーション側は「状態がこの値なら、この UI を表示する」と記述します。フレームワークは状態変化を検出し、必要な DOM 更新を調整します。
アプリケーション: state と UI の対応を記述する
フレームワーク: 更新を収集し、DOM 反映の順序を調整する
ブラウザ: style、layout、paint、composite を実行する
この分担により、開発者は「前の表示を消してから新しい表示を追加する」手順の多くを書かずに済みます。代わりに、状態の正本、コンポーネントの境界、リストの identity を正しく設計する責任があります。
React: UI ツリーを再計算して commit する
React は state 更新を契機にコンポーネントを再実行し、JSX から次の UI ツリーを求めます。前回の結果との対応付けを経て、実 DOM へ反映する commit を行います。レンダーは「DOM を今すぐ書き換える処理」ではないため、レンダー中は純粋に保ち、DOM の読み書きや購読はイベントハンドラまたは Effect に置きます。
React の重要な設計感覚は、再レンダーを異常と見なさないことです。まず正しい UI を state から導けるようにし、実測で問題がある箇所だけメモ化や state の分割を検討します。リストの key は対応付けと state の保持を左右するため、データの ID を渡します。
Vue: リアクティブな依存を追跡して更新する
Vue は ref や reactive で読まれた値と、コンポーネントのレンダー処理の依存関係を追跡します。値が変わると影響を受ける更新をスケジュールし、仮想 DOM を使って DOM 更新を行います。テンプレートはコンパイルされるため、静的な部分の扱いなど、テンプレート構造から得られる最適化情報も利用できます。
Vue では「何でも watch で DOM や別の state にコピーする」のではなく、表示から導ける値は computed またはテンプレートで表すのが基本です。更新後の DOM を一度読む必要があるときは nextTick()、DOM を使う外部ライブラリは onMounted / onUnmounted を境界にします。
Angular: テンプレートと変更検知で同期する
Angular ではテンプレートのバインディングと変更検知によって UI を同期します。コンポーネントはテンプレートにデータとイベントを宣言し、Angular がビュー更新を調整します。近年の Angular では Signals も、どの状態が変わったかを表現する中心的な選択肢です。
DOM を直接必要とする場面では、コンポーネントのホスト要素やビューのクエリを通じて扱います。Angular の公式ガイドは、レンダーコールバック内で DOM を読み書きし、通常のアプリケーションロジックには避けることを案内しています。テンプレートが管理する範囲を外部コードが変更しないよう、同じ所有権の原則を守ります。
Svelte: コンパイル時に更新手順を作る
Svelte はコンポーネントをビルド時にコンパイルし、どの state 変更でどの DOM 操作が必要かを、生成コードへより直接的に埋め込みます。そのため、実行時に汎用的な仮想 DOM ツリーを比較する方式とは異なる特徴があります。
ただし「仮想 DOM がないから常に速い」という結論にはなりません。実際の速度は、生成する DOM の量、JavaScript の処理、ネットワーク、ブラウザのレイアウト・描画、アプリの設計で決まります。Svelte でも状態を重複させれば UI は不整合になり、巨大なリストを全描画すればコストはかかります。
比較のための見取り図
| フレームワーク | 更新を判断する主な仕組み | DOM 操作が必要な境界 |
|---|---|---|
| React | 再レンダーした UI ツリーの対応付けと commit | ref、Effect、イベントハンドラ |
| Vue | リアクティブ依存の追跡と仮想 DOM による更新 | template ref、ライフサイクル、nextTick |
| Angular | テンプレートのバインディングと変更検知 | view query、レンダーコールバック |
| Svelte | コンパイル時に生成される細粒度の更新コード | bind:this、ライフサイクル、イベント |
表は内部実装のすべてを表すものではありません。各フレームワークは SSR、ハイドレーション、スケジューリング、開発時の検証など複数の経路を持ち、バージョンでも詳細が変わります。共通しているのは、宣言した UI が所有する DOM を外から不用意に変更しないことです。
選定と設計で見るべきこと
特定の DOM 更新戦略だけを理由にフレームワークを選ぶ場面は多くありません。チームの経験、エコシステム、SSR とルーティング、型安全性、コンポーネント設計、運用できる複雑さを総合して選びます。
どのフレームワークでも次の問いは共通です。
- UI を決める正本の state はどこにあるか
- 一つの DOM 範囲を誰が所有し、外部ライブラリとの境界はどこか
- リスト項目を一意に識別できるか
- 遅さは JavaScript、DOM、layout、paint のどこで起きているか
この問いに答えられる設計なら、内部の差分アルゴリズムの違いを必要以上にアプリコードへ漏らさずに済みます。
まとめ
React と Vue は仮想 DOM を利用しつつ異なる更新モデルを持ち、Angular はテンプレートと変更検知、Svelte はコンパイル時の更新コードを中心にします。いずれも最終的には実 DOM とブラウザの描画パイプラインに接続します。方式の名前だけで判断せず、状態、DOM の所有権、計測結果を基準に設計しましょう。