メインコンテンツにスキップ

🎨 Virtual DOMとは?

📖 定義

Virtual DOMは実際のDOMの軽量なコピーで、メモリ上にのみ存在するJavaScriptオブジェクトです。Reactのようなライブラリは、Virtual DOMを使用してUI変更を仮想的に処理した後、実際のDOMに最小限の変更のみを適用します。これにより、Webアプリケーションのパフォーマンスが大幅に向上します。

🎯 比喩で理解する

建築設計図の比喩

Virtual DOMを建築設計図に例えると:

実際の建物 (Real DOM)
├─ 建築費用が高い
├─ 修正に時間がかかる
└─ 解体して建て直すのが難しい

設計図 (Virtual DOM)
├─ 紙の上で自由に修正
├─ 速くて安価
├─ 複数案の比較が可能
└─ 最終案だけを実際の建物に反映

プロセス:
1. 設計図で何度も修正 (Virtual DOM)
2. 変更された部分のみを把握
3. 実際の建物に最小限だけ適用 (Real DOM)

こうすることで建築費用と時間を節約!

ワードプロセッサの比喩

メモ帳 (バニラJavaScript)
- タイピングするたびにすぐに保存
- 遅くて非効率的
- ファイル入出力の繰り返し

ワードプロセッサ (React + Virtual DOM)
- メモリ上で編集
- 変更を追跡
- 保存ボタンを押したときだけファイルに書き込み
- 速くて効率的

Virtual DOM = メモリバッファ
Real DOM = ディスクに保存

ゲームレンダリングの比喩

ゲーム開発では:

直接画面に描画 (Real DOM直接操作)
- ちらつきが発生
- パフォーマンス低下
- 部分更新が難しい

ダブルバッファリング (Virtual DOM方式)
1. 裏画面(バッファ)に絵を描く
2. 完成したら表画面と交換
3. スムーズなレンダリング
4. 変更されたピクセルだけを更新

Virtual DOMがダブルバッファリングと同じ役割!

⚙️ 動作原理

1. DOMとは?

// DOM (Document Object Model)
// HTMLをJavaScriptで操作できるインターフェース

<!DOCTYPE html>
<html>
<body>
<div id="root">
<h1>Hello</h1>
<p>World</p>
</div>
</body>
</html>

// DOMツリー
document
└─ html
└─ body
└─ div#root
├─ h1 ("Hello")
└─ p ("World")

// JavaScriptでDOM操作
document.getElementById('root').innerHTML = '<h1>Changed!</h1>';

2. Real DOMの問題点

// ❌ 非効率的なReal DOM操作

// 問題1: 毎回全体の再レンダリング
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = `Item ${i}`;
document.body.appendChild(div); // 1000回DOM操作!
}

// 問題2: レイアウト再計算 (Reflow)
element.style.width = '100px'; // Reflow発生
element.style.height = '100px'; // Reflow発生
element.style.margin = '10px'; // Reflow発生

// 問題3: 複雑なUI更新
// 一文字変わっても全体コンポーネント再生成
<div>
<Header />
<Content /> ← これだけが変わったのに
<Footer />
</div>
// 全体を作り直さなければならない!

// Real DOM操作が遅い理由:
// 1. ブラウザレンダリングエンジンとJavaScriptエンジン間の通信
// 2. Reflow (レイアウト再計算)
// 3. Repaint (画面の再描画)

3. Virtual DOM動作過程

// ✅ Virtual DOMの効率的な処理

// 1段階: 初期レンダリング
const initialVirtualDOM = {
type: 'div',
props: { id: 'app' },
children: [
{ type: 'h1', props: {}, children: ['Count: 0'] },
{ type: 'button', props: {}, children: ['Click'] }
]
};

// Real DOMに反映
<div id="app">
<h1>Count: 0</h1>
<button>Click</button>
</div>

// 2段階: 状態変更 (count = 1)
const newVirtualDOM = {
type: 'div',
props: { id: 'app' },
children: [
{ type: 'h1', props: {}, children: ['Count: 1'] }, // ← 変更された
{ type: 'button', props: {}, children: ['Click'] }
]
};

// 3段階: Diffing (差分計算)
const diff = {
path: ['div', 'h1', 'text'],
oldValue: 'Count: 0',
newValue: 'Count: 1'
};

// 4段階: Reconciliation (再調整)
// 最小限の変更だけReal DOMに適用
document.querySelector('h1').textContent = 'Count: 1';
// divとbuttonはそのまま維持!

4. Diffingアルゴリズム

// ReactのDiffingアルゴリズムの原理

// ルール1: 異なるタイプの要素は異なるツリーを生成
// 前
<div>
<Counter />
</div>

// 後
<span>
<Counter />
</span>

// 結果: divをspanに完全に置き換え
// Counterもアンマウント後、再マウント

// ルール2: keyを使用してリストアイテムを追跡
// ❌ keyなし
<ul>
<li>A</li>
<li>B</li>
</ul>

// 先頭にCを追加
<ul>
<li>C</li> // 全体を作り直す
<li>A</li>
<li>B</li>
</ul>

// ✅ key使用
<ul>
<li key="a">A</li>
<li key="b">B</li>
</ul>

// 先頭にCを追加
<ul>
<li key="c">C</li> // Cだけ追加
<li key="a">A</li> // そのまま
<li key="b">B</li> // そのまま
</ul>

// ルール3: 再帰的に子を比較
function diff(oldNode, newNode) {
// 1. タイプが異なれば置き換え
if (oldNode.type !== newNode.type) {
return { action: 'REPLACE', newNode };
}

// 2. 属性を比較
const propsDiff = diffProps(oldNode.props, newNode.props);

// 3. 子を比較 (再帰)
const childrenDiff = diffChildren(
oldNode.children,
newNode.children
);

return { propsDiff, childrenDiff };
}

5. Reconciliation (再調整)

// Reconciliation: Virtual DOMをReal DOMに反映

// Phase 1: Render Phase (非同期)
// - Virtual DOM比較
// - 変更事項を計算
// - 中断可能 (React 18 Fiber)

// Phase 2: Commit Phase (同期)
// - Real DOM更新
// - ライフサイクルメソッド実行
// - 中断不可能

// 例: リスト更新
const oldList = [
{ id: 1, name: 'A' },
{ id: 2, name: 'B' },
{ id: 3, name: 'C' }
];

const newList = [
{ id: 1, name: 'A' },
{ id: 3, name: 'C-updated' }, // 修正
{ id: 4, name: 'D' } // 追加
// id: 2 削除
];

// Reconciliation結果:
// - id:1 維持
// - id:2 削除
// - id:3 テキストだけ更新
// - id:4 追加

// 実際のDOM操作を最小化:
// remove(id:2)
// updateText(id:3, 'C-updated')
// append(id:4)

💡 実際の例

基本Virtual DOM実装

// 簡単なVirtual DOM実装 (教育用)

// 1. Virtual DOMノード生成
function createElement(type, props = {}, ...children) {
return {
type,
props,
children: children.flat()
};
}

// JSXなしで使用
const vdom = createElement(
'div',
{ id: 'app', className: 'container' },
createElement('h1', {}, 'Hello Virtual DOM'),
createElement('p', {}, 'This is a paragraph')
);

console.log(vdom);
// {
// type: 'div',
// props: { id: 'app', className: 'container' },
// children: [
// { type: 'h1', props: {}, children: ['Hello Virtual DOM'] },
// { type: 'p', props: {}, children: ['This is a paragraph'] }
// ]
// }

// 2. Virtual DOMをReal DOMに変換
function render(vnode) {
// テキストノード
if (typeof vnode === 'string' || typeof vnode === 'number') {
return document.createTextNode(vnode);
}

// 要素ノード
const element = document.createElement(vnode.type);

// 属性を適用
Object.entries(vnode.props || {}).forEach(([key, value]) => {
if (key === 'className') {
element.setAttribute('class', value);
} else if (key.startsWith('on')) {
// イベントリスナー
const event = key.substring(2).toLowerCase();
element.addEventListener(event, value);
} else {
element.setAttribute(key, value);
}
});

// 子をレンダリング (再帰)
(vnode.children || []).forEach(child => {
element.appendChild(render(child));
});

return element;
}

// 使用
const domElement = render(vdom);
document.body.appendChild(domElement);

Diffingアルゴリズム実装

// 3. Diffingアルゴリズム (簡単バージョン)
function diff(oldVNode, newVNode) {
// 1. 新しいノードがなければ削除
if (!newVNode) {
return { type: 'REMOVE' };
}

// 2. 以前のノードがなければ追加
if (!oldVNode) {
return { type: 'CREATE', newVNode };
}

// 3. タイプが異なれば置き換え
if (
typeof oldVNode !== typeof newVNode ||
(typeof oldVNode === 'string' && oldVNode !== newVNode) ||
oldVNode.type !== newVNode.type
) {
return { type: 'REPLACE', newVNode };
}

// 4. 同じタイプなら更新
if (newVNode.type) {
return {
type: 'UPDATE',
props: diffProps(oldVNode.props, newVNode.props),
children: diffChildren(oldVNode.children, newVNode.children)
};
}

// 5. 変更なし
return { type: 'NONE' };
}

// 属性を比較
function diffProps(oldProps = {}, newProps = {}) {
const patches = [];

// 変更または追加された属性
Object.keys(newProps).forEach(key => {
if (oldProps[key] !== newProps[key]) {
patches.push({ type: 'SET_PROP', key, value: newProps[key] });
}
});

// 削除された属性
Object.keys(oldProps).forEach(key => {
if (!(key in newProps)) {
patches.push({ type: 'REMOVE_PROP', key });
}
});

return patches;
}

// 子を比較
function diffChildren(oldChildren = [], newChildren = []) {
const patches = [];
const maxLength = Math.max(oldChildren.length, newChildren.length);

for (let i = 0; i < maxLength; i++) {
patches.push(diff(oldChildren[i], newChildren[i]));
}

return patches;
}

Patch適用

// 4. PatchをReal DOMに適用
function patch(parent, patches, index = 0) {
if (!patches) return;

const element = parent.childNodes[index];

switch (patches.type) {
case 'CREATE':
parent.appendChild(render(patches.newVNode));
break;

case 'REMOVE':
parent.removeChild(element);
break;

case 'REPLACE':
parent.replaceChild(render(patches.newVNode), element);
break;

case 'UPDATE':
// 属性を更新
patches.props.forEach(propPatch => {
if (propPatch.type === 'SET_PROP') {
element.setAttribute(propPatch.key, propPatch.value);
} else if (propPatch.type === 'REMOVE_PROP') {
element.removeAttribute(propPatch.key);
}
});

// 子を更新 (再帰)
patches.children.forEach((childPatch, i) => {
patch(element, childPatch, i);
});
break;
}
}

ReactでVirtual DOMを使用

import React, { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);

// Reactが自動的にVirtual DOMを処理
return (
<div className="counter">
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<button onClick={() => setCount(count - 1)}>
Decrement
</button>
</div>
);
}

// 内部動作:
// 1. count変更 → setState呼び出し
// 2. Reactが新しいVirtual DOMを生成
// 3. 以前のVirtual DOMと比較 (Diffing)
// 4. <h1>テキストだけが変更されたことを検出
// 5. Real DOMのテキストノードだけを更新
// 6. div、buttonはそのまま維持

// パフォーマンス比較:
// ❌ バニラJS: 全体div.innerHTMLを置き換え
// ✅ React: <h1>のテキストだけを更新

複雑なリストの例

import React, { useState } from 'react';

function TodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '運動する', done: false },
{ id: 2, text: '勉強する', done: false },
{ id: 3, text: '掃除する', done: false }
]);

const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};

const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};

return (
<div>
<h1>やることリスト</h1>
<ul>
{todos.map(todo => (
<li
key={todo.id} // keyはDiffing最適化に必須!
style={{
textDecoration: todo.done ? 'line-through' : 'none',
color: todo.done ? '#888' : '#000'
}}
>
<input
type="checkbox"
checked={todo.done}
onChange={() => toggleTodo(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}

// Virtual DOMの効率性:
// todoを1つだけチェックしても
// - ❌ 全体リストの再生成 (X)
// - ✅ 該当<li>のstyleだけ変更 (O)

// keyの重要性:
// keyがあれば:
// - アイテムの移動/削除時に効率的
// - DOMの再利用が可能
// - コンポーネントの状態を維持

// keyがなければ:
// - 全体リストの再生成
// - パフォーマンス低下
// - コンポーネントの状態損失の可能性

パフォーマンス最適化例

import React, { useState, useMemo, memo } from 'react';

// memoでコンポーネントのメモ化
const TodoItem = memo(function TodoItem({ todo, onToggle, onDelete }) {
console.log(`TodoItem ${todo.id} レンダリング`);

return (
<li>
<input
type="checkbox"
checked={todo.done}
onChange={() => onToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => onDelete(todo.id)}>削除</button>
</li>
);
});

function OptimizedTodoList() {
const [todos, setTodos] = useState([
{ id: 1, text: '運動する', done: false },
{ id: 2, text: '勉強する', done: false },
{ id: 3, text: '掃除する', done: false }
]);

// useMemoで計算結果をキャッシュ
const activeTodos = useMemo(() => {
console.log('アクティブなやることを計算');
return todos.filter(todo => !todo.done);
}, [todos]);

const toggleTodo = (id) => {
setTodos(todos.map(todo =>
todo.id === id ? { ...todo, done: !todo.done } : todo
));
};

const deleteTodo = (id) => {
setTodos(todos.filter(todo => todo.id !== id));
};

return (
<div>
<h1>やることリスト</h1>
<p>残りのやること: {activeTodos.length}</p>
<ul>
{todos.map(todo => (
<TodoItem
key={todo.id}
todo={todo}
onToggle={toggleTodo}
onDelete={deleteTodo}
/>
))}
</ul>
</div>
);
}

// 最適化の効果:
// 1. memo: todoを1つだけ変更すれば、該当TodoItemだけ再レンダリング
// 2. useMemo: todosが変更されたときだけactiveTodosを再計算
// 3. key: 効率的なリスト更新

// パフォーマンス比較:
// 最適化前: 10個のtodoのうち1個変更 → 10個すべて再レンダリング
// 最適化後: 10個のtodoのうち1個変更 → 1個だけ再レンダリング

Virtual DOMなし vs あるときの比較

// ❌ Virtual DOMなし (バニラJavaScript)
function updateWithoutVirtualDOM() {
const app = document.getElementById('app');
let count = 0;

// 毎回全体HTMLを再生成
setInterval(() => {
count++;
app.innerHTML = `
<div class="container">
<h1>Count: ${count}</h1>
<p>Current time: ${new Date().toLocaleTimeString()}</p>
<button>Click me</button>
</div>
`;
// 問題点:
// 1. 全体DOMツリーの再生成
// 2. イベントリスナーの消失
// 3. 入力中のinputの初期化
// 4. スクロール位置の初期化
// 5. アニメーションの中断
}, 1000);
}

// ✅ Virtual DOMあるとき (React)
function CounterWithVirtualDOM() {
const [count, setCount] = useState(0);
const [time, setTime] = useState(new Date());

useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
setTime(new Date());
}, 1000);

return () => clearInterval(timer);
}, []);

return (
<div className="container">
<h1>Count: {count}</h1>
<p>Current time: {time.toLocaleTimeString()}</p>
<button onClick={() => alert('Clicked!')}>Click me</button>
</div>
);
}

// 利点:
// 1. <h1>と<p>のテキストだけを更新
// 2. イベントリスナーを維持
// 3. コンポーネントの状態を維持
// 4. スムーズな更新
// 5. 最小限のDOM操作

実際のパフォーマンス測定

import React, { useState } from 'react';

function PerformanceComparison() {
const [items, setItems] = useState([]);
const [renderTime, setRenderTime] = useState(0);

const generateItems = (count) => {
const startTime = performance.now();

const newItems = Array.from({ length: count }, (_, i) => ({
id: i,
value: Math.random()
}));

setItems(newItems);

const endTime = performance.now();
setRenderTime(endTime - startTime);
};

return (
<div>
<h1>パフォーマンス比較</h1>
<div>
<button onClick={() => generateItems(100)}>
100個生成
</button>
<button onClick={() => generateItems(1000)}>
1000個生成
</button>
<button onClick={() => generateItems(10000)}>
10000個生成
</button>
</div>

<p>レンダリング時間: {renderTime.toFixed(2)}ms</p>

<div style={{ maxHeight: '400px', overflow: 'auto' }}>
{items.map(item => (
<div key={item.id} style={{ padding: '5px', borderBottom: '1px solid #eee' }}>
Item #{item.id}: {item.value.toFixed(4)}
</div>
))}
</div>
</div>
);
}

// 結果 (一般的な場合):
// バニラJS (全体innerHTML):
// - 100個: ~50ms
// - 1000個: ~500ms
// - 10000個: ~5000ms (カクつき)

// React (Virtual DOM):
// - 100個: ~10ms
// - 1000個: ~100ms
// - 10000個: ~1000ms (スムーズ)

// Virtual DOMのパフォーマンス向上:
// 初期レンダリングは似ているが、
// 更新時に5-10倍速い!

Chrome DevToolsで確認する

// React DevTools Profilerの使い方

// 1. React DevToolsをインストール
// Chrome ウェブストアから"React Developer Tools"をインストール

// 2. Profilerタブを開く
// - 録画ボタンをクリック
// - アプリで動作を実行
// - 録画を停止

// 3. 結果を分析
// - Flamegraph: コンポーネントのレンダリング時間
// - Ranked: レンダリング時間順位
// - Componentをクリック: なぜレンダリングされたのかを確認

function ProfiledComponent() {
const [count, setCount] = useState(0);

// Profilerでラップしてレンダリングパフォーマンスを測定
return (
<React.Profiler
id="Counter"
onRender={(id, phase, actualDuration) => {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
}}
>
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
</React.Profiler>
);
}

// コンソール出力:
// Counter (mount) took 2.3ms
// Counter (update) took 0.5ms ← Virtual DOMのおかげで速い!

🤔 よくある質問

Q1. Virtual DOMは常にReal DOMより速いですか?

A: いいえ、状況によって異なります:

// ✅ Virtual DOMが速い場合:
// 1. 複雑なUI更新
function ComplexUI() {
const [data, setData] = useState([...]);

// 多くのコンポーネントのうち一部だけが変更
return (
<div>
{data.map(item => <ComplexComponent key={item.id} {...item} />)}
</div>
);
// Virtual DOM: 変更された部分だけを更新
// Real DOM: 全体の再生成が必要
}

// 2. 頻繁な更新
function LiveCounter() {
const [count, setCount] = useState(0);

useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
}, 100); // 毎秒10回更新
return () => clearInterval(timer);
}, []);

return <h1>Count: {count}</h1>;
// Virtual DOM: テキストだけ変更
// Real DOM: 毎回DOM操作
}

// ❌ Virtual DOMが遅い場合:
// 1. 簡単なUI (オーバーヘッド)
function SimpleButton() {
return <button>Click</button>;
}
// Virtual DOM比較 + Real DOM更新
// vs
// Real DOM直接操作

// 2. 初期レンダリング
// Virtual DOM生成 + Diffing + Real DOM生成
// vs
// Real DOM直接生成

// 結論:
// Virtual DOMの利点は「更新パフォーマンス」
// 初期ロードは少し遅い可能性があるが、
// 複雑なアプリでは全体的に速い!

Q2. keyはなぜ重要ですか?

A: keyは、Reactがどのアイテムが変更/追加/削除されたかを識別するために使用されます:

// ❌ keyなし (非効率的)
function BadList() {
const [items, setItems] = useState(['A', 'B', 'C']);

return (
<ul>
{items.map(item => <li>{item}</li>)}
</ul>
);
}

// 先頭に'D'を追加すると:
// 前: <li>A</li> <li>B</li> <li>C</li>
// 後: <li>D</li> <li>A</li> <li>B</li> <li>C</li>

// Reactはkeyがなければ:
// - 0番目: A → D (変更)
// - 1番目: B → A (変更)
// - 2番目: C → B (変更)
// - 3番目: なし → C (追加)
// 結果: 4つすべてを更新! (非効率)

// ✅ key使用 (効率的)
function GoodList() {
const [items, setItems] = useState([
{ id: 'a', text: 'A' },
{ id: 'b', text: 'B' },
{ id: 'c', text: 'C' }
]);

return (
<ul>
{items.map(item => (
<li key={item.id}>{item.text}</li>
))}
</ul>
);
}

// 先頭に'D'を追加すると:
// 前: key=a, key=b, key=c
// 後: key=d, key=a, key=b, key=c

// Reactはkeyで識別:
// - key=a: そのまま (維持)
// - key=b: そのまま (維持)
// - key=c: そのまま (維持)
// - key=d: 新規追加
// 結果: 1つだけ追加! (効率的)

// keyのルール:
// 1. 兄弟間で一意でなければならない
// 2. 変わってはいけない
// 3. 予測可能でなければならない

// ❌ 悪いkeyの例:
{items.map((item, index) => <li key={index}>{item}</li>)}
// インデックスは順序が変わると問題発生!

{items.map(item => <li key={Math.random()}>{item}</li>)}
// 毎回新しいkey → 常に再生成!

// ✅ 良いkeyの例:
{items.map(item => <li key={item.id}>{item}</li>)}
// データの一意なIDを使用

{items.map(item => <li key={item.email}>{item}</li>)}
// 一意な属性を使用

Q3. Virtual DOMを直接操作できますか?

A: いいえ、Reactが自動的に処理します。ただし最適化は可能です:

import React, { useState, useMemo, useCallback, memo } from 'react';

// 1. React.memo - コンポーネントのメモ化
const ExpensiveComponent = memo(function ExpensiveComponent({ value }) {
console.log('ExpensiveComponent レンダリング');

// 複雑な計算
const result = useMemo(() => {
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum + value;
}, [value]);

return <div>Result: {result}</div>;
});

// propsが同じなら再レンダリングをスキップ!

// 2. useMemo - 値のメモ化
function ListComponent({ items }) {
// itemsが変更されたときだけ再計算
const sortedItems = useMemo(() => {
console.log('ソート中...');
return [...items].sort((a, b) => a.value - b.value);
}, [items]);

return (
<ul>
{sortedItems.map(item => (
<li key={item.id}>{item.value}</li>
))}
</ul>
);
}

// 3. useCallback - 関数のメモ化
function Parent() {
const [count, setCount] = useState(0);
const [other, setOther] = useState(0);

// countが変更されたときだけ新しい関数を生成
const handleClick = useCallback(() => {
setCount(c => c + 1);
}, []);

return (
<div>
<Child onClick={handleClick} />
<button onClick={() => setOther(other + 1)}>
Other: {other}
</button>
</div>
);
}

const Child = memo(function Child({ onClick }) {
console.log('Child レンダリング');
return <button onClick={onClick}>Click</button>;
});

// other変更時、Childは再レンダリングされない!

// 4. shouldComponentUpdate / PureComponent (クラス型)
class OptimizedComponent extends React.PureComponent {
render() {
return <div>{this.props.value}</div>;
}
}

// propsが変更されなければ再レンダリングしない

// 5. 条件付きレンダリングの最適化
function ConditionalRendering({ showDetails, basicInfo, detailInfo }) {
return (
<div>
<BasicInfo data={basicInfo} />
{showDetails && <DetailInfo data={detailInfo} />}
</div>
);
}

// showDetailsがfalseの場合、DetailInfoはVirtual DOMにもない!

Q4. Virtual DOMを使わないフレームワークもありますか?

A: はい、Svelteのようなフレームワークは別の方式を使用します:

// React (Virtual DOM使用)
function Counter() {
const [count, setCount] = useState(0);

return (
<div>
<h1>Count: {count}</h1>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}

// ランタイムで:
// 1. Virtual DOM生成
// 2. Diffing
// 3. Real DOM更新

// Svelte (コンパイル時の最適化)
<script>
let count = 0;
</script>

<div>
<h1>Count: {count}</h1>
<button on:click={() => count += 1}>+</button>
</div>

// コンパイル結果:
function update_count() {
h1.textContent = `Count: ${count}`; // 直接DOM操作!
}

// 長所と短所の比較:

// Virtual DOM (React, Vue)
// 長所:
// - 宣言的UI
// - 予測可能なパフォーマンス
// - 豊富なエコシステム

// 短所:
// - メモリオーバーヘッド
// - Diffingコスト
// - バンドルサイズが大きい

// No Virtual DOM (Svelte)
// 長所:
// - 高速なパフォーマンス
// - 小さいバンドルサイズ
// - 低いメモリ使用量

// 短所:
// - エコシステムが小さい
// - 複雑なアプリでは?
// - 学習曲線

// Solid.js (Fine-grained Reactivity)
// Virtual DOMなしでReact構文を使用
import { createSignal } from 'solid-js';

function Counter() {
const [count, setCount] = createSignal(0);

return (
<div>
<h1>Count: {count()}</h1>
<button onClick={() => setCount(count() + 1)}>+</button>
</div>
);
}

// 変更された部分だけを正確に更新!
// Virtual DOMなしでも効率的!

Q5. React 18のConcurrent Renderingとは?

A: Virtual DOM更新を中断/再開できるようにする機能です:

import React, { useState, useTransition, Suspense, useDeferredValue } from 'react';

// 1. useTransition - 低優先度の更新
function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
const [isPending, startTransition] = useTransition();

const handleChange = (e) => {
// すぐに更新 (高優先度)
setQuery(e.target.value);

// 後で更新 (低優先度)
startTransition(() => {
// 重い計算
const newResults = expensiveSearch(e.target.value);
setResults(newResults);
});
};

return (
<div>
<input
value={query}
onChange={handleChange}
placeholder="検索..."
/>
{isPending && <span>検索中...</span>}
<ul>
{results.map(result => (
<li key={result.id}>{result.title}</li>
))}
</ul>
</div>
);
}

// 動作:
// 1. ユーザーが素早くタイピング
// 2. queryはすぐに更新 (inputの反応性を維持)
// 3. resultsはゆっくり更新 (バックグラウンド)
// 4. 新しいタイピングが入ると、以前のresults更新を中断!

// 2. Suspense - ローディング状態管理
const LazyComponent = React.lazy(() => import('./Heavy'));

function App() {
return (
<Suspense fallback={<div>ロード中...</div>}>
<LazyComponent />
</Suspense>
);
}

// 3. useDeferredValue - 値の遅延
function FilteredList({ items, query }) {
const deferredQuery = useDeferredValue(query);

const filteredItems = useMemo(() => {
return items.filter(item =>
item.name.includes(deferredQuery)
);
}, [items, deferredQuery]);

return (
<ul>
{filteredItems.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}

// Concurrent Renderingの利点:
// 1. 応答性の維持: UIがカクつかない
// 2. 優先度: 重要な更新を先に
// 3. 中断可能: 不要な作業をスキップ
// 4. スムーズな遷移: 突然の変化を防止

// 内部動作:
// Time Slicing:
// [タイピング] [レンダリング] [タイピング] [レンダリング] ...
// 作業を複数の小さな断片に分けて実行

// 優先度:
// Urgent (すぐに): クリック、タイピング、ホバー
// Transition (後で): 検索結果、フィルタリング
// Deferred (最後): 分析、ログ

🎓 次のステップ

Virtual DOMを理解したら、次を学習してみましょう:

  1. Reactとは? (ドキュメント作成予定) - Virtual DOMを使用する代表的なライブラリ
  2. CSR vs SSR vs SSG - レンダリング戦略の比較
  3. Webパフォーマンス最適化 (ドキュメント作成予定) - Virtual DOMを活用した最適化

🎬 まとめ

Virtual DOMは現代のWebフレームワークの核心技術です:

  • 概念: Real DOMの軽量なコピー
  • 原理: Diffingで変更事項を計算後、最小限の更新
  • 利点: 複雑なUI更新のパフォーマンス向上
  • 核心: 宣言的プログラミングで開発者エクスペリエンスの改善

Virtual DOMのおかげで、私たちはパフォーマンスの心配なくUIロジックに集中できます!