フレームワークごとの DOM 更新戦略

React、Vue、Angular、Svelte の DOM 更新モデルを比較し、仮想 DOM の有無ではなく責務と境界で選ぶ視点を学ぶ。

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 の所有権、計測結果を基準に設計しましょう。

Sources