



WithCodeMedia-1-pc
WithCodeMedia-2-pc
WithCodeMedia-3-pc
WithCodeMedia-4-pc




WithCodeMedia-1-sp
WithCodeMedia-2-sp
WithCodeMedia-3-sp
WithCodeMedia-4-sp








生徒Reactを勉強し始めたんですが、「宣言的UI」と「命令的UI」の違いが正直よくわかりません。Vanilla JSとどう違うんでしょうか?



よーく聞くんだぞ。宣言的UIと命令的UIの違いは、「誰がDOMの更新を担当するか」というたった1点の違いじゃ。ここを理解すると、なぜReactの書き方がVanilla JSと全然違うのかが一気に腑に落ちるんじゃ。今日はコード例・比較表・実践パターン5選を使ってわかりやすく解説するぞい!
宣言的UIと命令的UIの違いは「UIの更新責任が誰にあるか」という1点だけです。命令的UI(Vanilla JS)では開発者がDOMの更新手順をすべて書き、宣言的UI(React)では「状態に応じたUIのあるべき姿」を宣言するだけでフレームワークが自動でDOMを更新します。この概念を理解していないと、Reactを使っていてもjQueryのような命令的なコードに陥りがちです。
あわせて読みたい:
【2026年版】Web制作・フロントエンドエンジニアになるための最強ロードマップ(学習順序の全体像)
JavaScriptの非同期処理をゼロから理解する|Promise・async/awaitを図解で解説(JavaScript基礎)


【宣言的UIと命令的UIの本質的な違い】
命令的UI(Vanilla JS・jQuery など)
→ 「UIの更新の責任」を開発者が持つ
→ 状態が変わるたびに「どのDOMをどう変えるか」を自分で書く
宣言的UI(React・Vue など)
→ 「UIの更新の責任」をフレームワークが持つ
→ 開発者は「状態に応じてUIがどうなってほしいか」を宣言するだけ
→ DOMの更新はフレームワークが自動で行う
命令的UIでは、状態が変わったとき、どのDOMをどのように更新するかという手順をすべて開発者が書きます。
<!-- Vanilla JS による命令的UIの例(ログイン状態の切り替え) -->
<p id="message"></p>
<button id="btn">ログイン</button>
<script>
let isLoggedIn = false;
const message = document.getElementById('message');
const btn = document.getElementById('btn');
// DOM の初期状態を開発者が手動でセットする
message.textContent = 'ログインしてください。';
btn.addEventListener('click', () => {
isLoggedIn = !isLoggedIn;
// 状態が変わるたびに「どのDOMをどう変えるか」を開発者が書く
if (isLoggedIn) {
message.textContent = 'ようこそ!';
btn.textContent = 'ログアウト';
} else {
message.textContent = 'ログインしてください。';
btn.textContent = 'ログイン';
}
});
</script>
宣言的UIでは、「状態がこの値のとき、UIはこうあってほしい」というゴールだけを宣言します。DOMをどう更新するかはReactが自動で判断してくれます。
// React による宣言的UIの例(同じログイン状態の切り替え)
import { useState } from 'react';
function LoginMessage() {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div>
{/* 状態に応じたUIの「あるべき姿」を宣言するだけ */}
<p>{isLoggedIn ? 'ようこそ!' : 'ログインしてください。'}</p>
<button onClick={() => setIsLoggedIn(!isLoggedIn)}>
{isLoggedIn ? 'ログアウト' : 'ログイン'}
</button>
</div>
);
}
// isLoggedIn が変わると、ReactがDOMを自動更新してくれる
// 開発者はDOM操作のコードを一切書かない
| 観点 | 宣言的UI(React・Vue) | 命令的UI(Vanilla JS・jQuery) |
|---|---|---|
| UIの更新責任 | フレームワーク | 開発者 |
| 開発者が書くこと | 「状態に応じたUIのあるべき姿」 | 「どのDOMをどう更新するか」の手順 |
| DOM操作 | 不要(自動で反映) | 手動で操作が必要 |
| コードの読みやすさ | UIの状態が見た目でわかりやすい | DOMとロジックが入り混じりやすい |
| 状態が増えたとき | JSXに状態を追加するだけ | 全状態パターンの更新コードを追加する必要がある |
| 主な技術 | React・Vue・Svelte | Vanilla JS・jQuery |
多くの初学者がはまる誤解として「三項演算子で条件分岐を書けば宣言的」というものがあります。これは正確ではありません。


// ❌ 三項演算子を使っているが、命令的UIのコード
const isLoggedIn = true;
// 表示するテキストを三項演算子で決めているが…
const message = isLoggedIn ? 'ようこそ!' : 'ログインしてください。';
// DOMへの「反映方法」は開発者が記述している → 命令的UI
const p = document.createElement('p');
p.textContent = message;
document.body.appendChild(p);
【宣言的かどうかを判断する1つの問い】
「DOMをどう更新するか、を開発者が書いているか?」
✅ 宣言的UI:
→ 開発者は「UIのあるべき姿」だけ書く
→ DOMの更新方法はフレームワークが判断する
❌ 命令的UI(三項演算子を使っていても):
→ 最終的に document.createElement / textContent / appendChild
などでDOM更新の「方法」を開発者が書いている
▶ 判断の本質は「構文の見た目」ではなく「DOM更新の責任がどこにあるか」
重要なのは、Reactを使っていても命令的なコードを書くことはできます。フレームワークが宣言的に書ける手段を提供しているだけで、強制しているわけではありません。
// ❌ Reactの中で命令的なコードを書いてしまっているケース
import { useEffect } from 'react';
function BadExample() {
useEffect(() => {
// document を直接操作 → 命令的UI
const el = document.getElementById('title');
if (el) el.textContent = '変更後のタイトル';
}, []);
return <h1 id="title">元のタイトル</h1>;
}
// ✅ Reactらしい宣言的なコード
import { useState } from 'react';
function GoodExample() {
const [title, setTitle] = useState('元のタイトル');
return (
<div>
<h1>{title}</h1> {/* Reactがstateに応じてDOMを更新する */}
<button onClick={() => setTitle('変更後のタイトル')}>変更</button>
</div>
);
}


useRef はDOM参照のために使うフックですが、取得したDOMを直接書き換えると命令的になります。
import { useRef } from 'react';
// ❌ useRef でDOMを直接書き換えている(命令的)
function BadCounter() {
const countRef = useRef(null);
let count = 0;
const handleClick = () => {
count++;
// Reactの管理外でDOMを直接変更している
if (countRef.current) {
countRef.current.textContent = `カウント: ${count}`;
}
};
return (
<div>
<p ref={countRef}>カウント: 0</p>
<button onClick={handleClick}>+1</button>
</div>
);
}
// ✅ useState で宣言的に書く
import { useState } from 'react';
function GoodCounter() {
const [count, setCount] = useState(0);
return (
<div>
<p>カウント: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
import { useEffect, useState } from 'react';
// ❌ useEffect 内でDOMを直接操作(命令的)
function BadThemeToggle() {
const [isDark, setIsDark] = useState(false);
useEffect(() => {
// Reactの管理外でDOMを直接操作している
document.body.style.backgroundColor = isDark ? '#333' : '#fff';
document.body.style.color = isDark ? '#fff' : '#333';
}, [isDark]);
return (
<button onClick={() => setIsDark(!isDark)}>
{isDark ? 'ライトモード' : 'ダークモード'}に切り替え
</button>
);
}
// ✅ クラス名をstateで管理して宣言的に書く
function GoodThemeToggle() {
const [isDark, setIsDark] = useState(false);
return (
// className を state から導出するのが宣言的
<div className={isDark ? 'theme-dark' : 'theme-light'}>
<button onClick={() => setIsDark(!isDark)}>
{isDark ? 'ライトモード' : 'ダークモード'}に切り替え
</button>
</div>
);
}
// ❌ React コンポーネント内で document.querySelector を使う(命令的)
function BadModal() {
const openModal = () => {
// Reactの外からDOMを操作している
const modal = document.querySelector('#modal');
if (modal) modal.style.display = 'block';
};
return (
<div>
<div id="modal" style={{ display: 'none' }}>モーダルの中身</div>
<button onClick={openModal}>開く</button>
</div>
);
}
// ✅ isOpen を state で管理して宣言的に書く
import { useState } from 'react';
function GoodModal() {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
{isOpen && <div>モーダルの中身</div>}
<button onClick={() => setIsOpen(true)}>開く</button>
{isOpen && (
<button onClick={() => setIsOpen(false)}>閉じる</button>
)}
</div>
);
}
【useRef + DOM直接操作が適切なケース】
✅ フォーカスの制御
→ input.focus() は React では宣言的に表現しにくい
✅ スクロール位置の制御
→ element.scrollIntoView() など
✅ サードパーティのDOMライブラリとの統合
→ Chart.js・Google Maps など React を介さないライブラリ
✅ アニメーションの細かな制御
→ CSS では対応できない複雑なアニメーション
→ これらは「例外的に命令的な操作が必要なケース」として認識しておく


| メリット | 内容 | 具体例 |
|---|---|---|
| コードの可読性が高い | 状態を見ればUIの表示が予測できる | {isLoggedIn ? '...' : '...'} でログイン状態が一目でわかる |
| バグが減る | DOMの更新をフレームワークに任せるため「更新し忘れ」が起きない | 10個の状態が変わってもReactが全部対応するDOMを自動更新する |
| テストが書きやすい | 「state → UI」の変換ロジックのみテストすればよく、DOM操作テストが不要 | render(<LoginMessage isLoggedIn={true} />) で表示を検証できる |
| 状態管理がシンプル | UIの状態をすべてstateで一元管理できる | フォームの入力値・エラー・ローディング状態をstateで管理 |
| 再利用・保守しやすい | コンポーネント単位で「状態とUI」がセット | Buttonコンポーネントをどこでも再利用できる |
【宣言的UIのデメリット】
❌ 学習コストが高い
→ 「状態を変えたらUIが変わる」という思考モデルに慣れるまで時間がかかる
→ Vanilla JS で育った開発者は最初に戸惑いやすい
❌ パフォーマンスの最適化が難しい場合がある
→ 不要な再レンダリングが起きやすい
→ React.memo / useMemo / useCallback での最適化が必要になる
❌ 細かなDOM制御が必要なときに回りくどくなる
→ フォーカス制御・スクロール・サードパーティライブラリなど
→ useRef を使った命令的コードを「例外として」書く必要がある
<!-- Vanilla JS による命令的TODOリスト -->
<input id="todo-input" placeholder="TODOを入力">
<button id="add-btn">追加</button>
<ul id="todo-list"></ul>
<script>
const todos = [];
const input = document.getElementById('todo-input');
const btn = document.getElementById('add-btn');
const list = document.getElementById('todo-list');
btn.addEventListener('click', () => {
if (!input.value.trim()) return;
const todo = { text: input.value, done: false };
todos.push(todo);
input.value = '';
// DOMを手動で生成・追加する(命令的)
const li = document.createElement('li');
li.textContent = todo.text;
li.addEventListener('click', () => {
todo.done = !todo.done;
li.style.textDecoration = todo.done ? 'line-through' : 'none';
});
const deleteBtn = document.createElement('button');
deleteBtn.textContent = '削除';
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
todos.splice(todos.indexOf(todo), 1);
list.removeChild(li); // DOMを手動で削除する(命令的)
});
li.appendChild(deleteBtn);
list.appendChild(li);
});
</script>
// React による宣言的UIのTODOリスト
import { useState } from 'react';
function TodoApp() {
const [todos, setTodos] = useState([]);
const [input, setInput] = useState('');
const addTodo = () => {
if (!input.trim()) return;
// todos(state)を更新するだけ → React が自動でUIを更新する
setTodos([...todos, { id: Date.now(), text: input, done: false }]);
setInput('');
};
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));
};
// UIのあるべき姿を宣言するだけ。DOM操作は一切書かない
return (
<div>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="TODOを入力"
/>
<button onClick={addTodo}>追加</button>
<ul>
{todos.map(todo => (
<li key={todo.id} style={{ textDecoration: todo.done ? 'line-through' : 'none' }}>
<span onClick={() => toggleTodo(todo.id)}>{todo.text}</span>
<button onClick={() => deleteTodo(todo.id)}>削除</button>
</li>
))}
</ul>
</div>
);
}
【TODOリスト実装の比較まとめ】
Vanilla JS(命令的) React(宣言的)
─────────────────────────────────────────────
DOM を手動で createElement → JSXで宣言するだけ
DOM を手動で appendChild → Reactが自動挿入
DOM を手動で removeChild → state から除外するだけ
style を手動で書き換える → state から style を導出
変更のたびに再描画ロジック → setTodos するだけ
→ 「状態を更新すれば、UIは自動でついてくる」のが宣言的UIの本質
JavaScriptの基礎はReactを学ぶ前にしっかり理解しておくことを強く推奨します。変数・関数・配列操作(map・filter・spread)・非同期処理(Promise・async/await)を理解した上でReactを学ぶと、「なぜこういう書き方をするのか」が腑に落ちます。特にmap()・filter()・スプレッド演算子はReactのコードで頻繁に登場するため必須の前提知識です。
絶対NGではありませんが、stateで管理できる場合はuseEffect+DOM直接操作を避けるのが原則です。サードパーティライブラリの初期化(Google Maps・Chart.jsなど)やフォーカス制御・スクロールなど「本質的にDOMの操作が必要なケース」は例外として許容されます。判断の目安は「これはstateで表現できるか?」を自問することです。
はい、Vue・Svelte・Angularなども宣言的UIのフレームワークです。いずれも「状態を宣言したらフレームワークがDOMを更新する」というモデルを採用しています。宣言的UIの概念はReact固有ではなく、モダンフロントエンド全体に共通する設計思想です。
適切に実装すれば、宣言的UIが命令的UIよりパフォーマンスで劣ることはほぼありません。Reactは仮想DOM(Virtual DOM)を使い、変更があった箇所だけ効率的にDOMを更新します。ただし、不要な再レンダリングが多い場合はReact.memo・useMemo・useCallbackでの最適化が必要になることがあります。
よくあるケースです。コードが複雑になる主な原因として、(1) stateを複数のコンポーネントに分散させている、(2) useEffectの依存配列が複雑になっている、(3) 1つのコンポーネントが多くの責任を持ちすぎている、の3点が挙げられます。「1コンポーネント1責任」と「stateを1つ上のコンポーネントで管理するstate lifting」を意識すると整理しやすくなります。



なんとなくわかってきたんですが、Reactを使えばVanilla JSは覚えなくていいんじゃないですか?



それは大きな誤解じゃぞ!ReactはJavaScriptの上に成り立っているから、JavaScriptが弱いとReactでつまずき続けることになるんじゃ。map・filter・async/awaitといったJSの基礎がしっかりしていてこそ、宣言的UIの恩恵を最大限に受けられるんじゃ。WithCodeで基礎をしっかり固めた上でReactに進むのが最短ルートじゃぞ!



なるほど!「宣言的UIはDOMの更新をフレームワークに任せる」という本質がやっとつかめました。まずJSの基礎を固めてからReactに進みます!
React宣言的UIの本質は「状態を変えるだけでUIが追随する」という設計モデルであり、JavaScriptの基礎とセットで理解することで初めてその恩恵を最大限に受けられます。
副業・フリーランスが主流になっている今こそ、自らのスキルで稼げる人材を目指してみませんか?
未経験でも心配することはありません。初級コースを受講される方の大多数はプログラミング未経験です。まずは無料カウンセリングで、悩みや不安をお聞かせください!
公式サイト より
今すぐ
無料カウンセリング
を予約!