Virtual DOMとの比較(Reactなどのライブラリとの関係)

以下では、DOMの直接操作と、React等が採用する**Virtual DOM(VDOM)**を対比しながら、「なぜ」「いつ」どちらを選ぶべきか、具体的なベストプラクティスまで整理します。

概要

  • DOM(通常のDOM): ブラウザが持つ実体のノードツリー。スタイル計算・レイアウト・描画と密接に結びつき、変更は即座にレンダリングコストに波及し得る。

  • Virtual DOM: JSオブジェクトで表現した“仮想”ツリー。アプリの状態→VDOMを再生成→差分(diff)計算→最小限の実DOMパッチを適用、という手順で更新を最適化・抽象化する。

仕組み(Reactを例に)

  1. 宣言的UI: stateが変わるたびにrender()の結果(VDOM)が作られる。

  2. 差分(Reconciliation): 新旧VDOMを比較し、必要な実DOM操作のリストに還元する。

  3. バッチ更新: 複数の状態更新をまとめて1フレーム内でDOMへ反映。

  4. 並行・優先度制御(React 18+): 重い更新を分割し、優先度の高いインタラクションを先に処理(Concurrent Rendering)。

  5. キー付きリスト: keyによりノード同一性を保証し、不要な再生成や移動コストを削減。

性能モデル(どこにコストが乗るか)

  • 直接DOM:
    コスト ≈ 変更回数 ×(スタイル再計算 + レイアウト + ペイント + 合成)

  • VDOM:
    コスト ≈ VDOM生成 + VDOM差分 + 実DOM最小パッチ +(以下同上のレンダリングコスト)

ポイント: VDOMは「無駄なDOM変更を減らす」恩恵がある一方、VDOM生成とdiff自体のJSコストが必ず乗る。中〜大規模UIや頻繁な部分差し替えでは得をしやすいが、超軽量UIやピンポイント更新では素のDOMが勝つこともある。

いつVirtual DOMが有利か

  • 状態遷移が複雑で、影響範囲の手計算最適化が難しい。

  • 頻繁な更新だが、実際に変わるDOMは部分的(VDOMが差分を小さく保つ)。

  • 可読性・保守性を優先(宣言的UI、部品化、テスト容易性)。

  • SSR/CSR切替・ハイドレーションなどフレームワーク機能を活用したい。

いつ直接DOMが有利か

  • UIが小規模・静的で、更新箇所が明確。

  • 高頻度・超軽量な数値表示や属性切替のみ(例えばtextContentclassList.toggle)。

  • キャンバスやWebGL主体、あるいは大規模リストの単純なスクロール描画(仮想スクロールで十分)。

React/VDOM側のベストプラクティス

  • keyを安定化: インデックスではなく一意IDを使う。
    悪い例: <li key={index}> → 並び替えで再マウントが多発
    良い例: <li key={user.id}>

  • 再レンダリング抑制:

    • 関数コンポーネントはReact.memoでprops変化時のみ再描画。

    • useMemo/useCallbackで高コスト計算・コールバックの再生成を抑える。

    • 大きなリストは仮想化react-window, react-virtualized)。

  • バッチ更新を活用: 同期的に多数のsetStateを呼ばない設計。イベントループ内で自然にバッチ化される。

  • スタイル変化をまとめる: スタイルを細かく都度変更せず、クラス切替中心に。

  • 重い処理は並行特性や遅延/分割: startTransitionで優先度を下げる、コード分割で初期レンダを軽く。

  • SSR/ハイドレーション: 初期表示を高速化し、クライアントで差分活用。

直接DOM側のベストプラクティス

  • 変更をまとめる: DocumentFragmentに構築→一括挿入。

    js
    const frag = document.createDocumentFragment(); for (const item of data) { const li = document.createElement('li'); li.textContent = item; frag.appendChild(li); } ul.appendChild(frag);
  • レイアウトスラッシング回避: 連続で「読み→書き→読み」を交互にしない。読み取りを先に固め、書き込みを後に固める。必要ならgetBoundingClientRect等の強制同期レイアウトを最小化。

  • アニメーションはCSS/transform中心: transform/opacityは合成レイヤでコストが低い。top/left/width/height更新は避ける。

  • requestAnimationFrameで描画タイミングに整列し、頻繁更新のスロットリング・デバウンスを行う。

  • 長いHTML挿入はinnerHTML一括が速い場面もあるが、信頼できるソースに限定(XSS注意)。

典型シナリオ比較

  • 大規模フォーム・ダッシュボード: 依存関係が複雑 → VDOM/React有利(宣言的で安全、差分が効く)。

  • 単純ウィジェットのトグル: 直接DOMで十分(classList.toggle等で1回の更新)。

  • 巨大リスト(1万件): どちらにせよ仮想スクロールが鍵。Reactならreact-window、素のDOMなら自前の可視領域レンダ。

実務でのハイブリッド戦略

  • Reactアプリ内で一部だけ直接DOMuseRefで要素参照、外部ライブラリ制御)を使うのは普通。

  • 逆に、素のアプリに小さなReactマイクロフロントエンドを埋め込む選択もある。

計測と診断

  • 計測なくして最適化なし: Performance Panel、フレームタイム、Recalculate Style/Layout、スクリプト自己時間、ガベージコレクションを確認。

  • ReactならProfilerで再レンダリング原因、コミット時間、メモ化効果を検証。

  • 実機・低性能端末や低速CPUスロットリングで体感を必ずチェック。

よくある誤解

  • 「VDOMは常に速い」: 小規模・単純な更新ではVDOM生成+diffのオーバーヘッドが負けることがある。

  • 「直接DOMは保守が難しい」: アーキテクチャと規約、薄い抽象(小さなView層)を整えれば中規模まで十分実用。

  • 「キーは何でもよい」: 不安定なキーは差分精度を落とし、再マウントやステート喪失を招く。

まとめ(指針)

  • 複雑さ・頻度・可読性で選ぶ:

    • 画面状態が複雑・更新頻度が高い → VDOM(React等)

    • 小さく単純・局所更新 → 直接DOM

  • どちらを選んでも、変更のバッチ化・無駄なレイアウト回数の削減・リスト仮想化・計測による検証が性能の要となる。

ChatGPT5 生成日:2025/09/11