WithCodeMedia-1-pc
previous arrowprevious arrow
next arrownext arrow

WithCodeMedia-1-sp
previous arrowprevious arrow
next arrownext arrow

React宣言的UIと命令的UIの違いと使い分け方【図解&コード付き完全ガイド】

生徒

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

ペン博士

よーく聞くんだぞ。宣言的UIと命令的UIの違いは、「誰がDOMの更新を担当するか」というたった1点の違いじゃ。ここを理解すると、なぜReactの書き方がVanilla JSと全然違うのかが一気に腑に落ちるんじゃ。今日はコード例・比較表・実践パターン5選を使ってわかりやすく解説するぞい!

目次

この記事でわかること

  • 宣言的UIと命令的UIの本質的な違い(比較表・コード例付き)
  • 「三項演算子を使えば宣言的」という誤解と正しい判断基準
  • Reactで命令的になってしまう3つのパターンと宣言的な書き換え方
  • useRef・DOM操作が適切なケースと例外の見極め方
  • TODOリストで学ぶVanilla JSとReactの実装比較

宣言的UIと命令的UIの違いは「UIの更新責任が誰にあるか」という1点だけです。命令的UI(Vanilla JS)では開発者がDOMの更新手順をすべて書き、宣言的UI(React)では「状態に応じたUIのあるべき姿」を宣言するだけでフレームワークが自動でDOMを更新します。この概念を理解していないと、Reactを使っていてもjQueryのような命令的なコードに陥りがちです。


宣言的UIと命令的UI:「誰がDOMを更新するか」が唯一の違い

宣言的UIと命令的UIの責任範囲の違いを示す図
【宣言的UIと命令的UIの本質的な違い】

命令的UI(Vanilla JS・jQuery など)
→ 「UIの更新の責任」を開発者が持つ
→ 状態が変わるたびに「どのDOMをどう変えるか」を自分で書く

宣言的UI(React・Vue など)
→ 「UIの更新の責任」をフレームワークが持つ
→ 開発者は「状態に応じてUIがどうなってほしいか」を宣言するだけ
→ DOMの更新はフレームワークが自動で行う

命令的UI:「どうするか」の手順を書く

命令的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では、「状態がこの値のとき、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 vs 命令的UI:一覧比較表

観点宣言的UI(React・Vue)命令的UI(Vanilla JS・jQuery)
UIの更新責任フレームワーク開発者
開発者が書くこと「状態に応じたUIのあるべき姿」「どのDOMをどう更新するか」の手順
DOM操作不要(自動で反映)手動で操作が必要
コードの読みやすさUIの状態が見た目でわかりやすいDOMとロジックが入り混じりやすい
状態が増えたときJSXに状態を追加するだけ全状態パターンの更新コードを追加する必要がある
主な技術React・Vue・SvelteVanilla JS・jQuery

「三項演算子を使えば宣言的UI」は間違い|正しい判断基準

多くの初学者がはまる誤解として「三項演算子で条件分岐を書けば宣言的」というものがあります。これは正確ではありません。

宣言的UIに関するよくある誤解を解説する図
// ❌ 三項演算子を使っているが、命令的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を使っていても命令的なコードを書くことはできます。フレームワークが宣言的に書ける手段を提供しているだけで、強制しているわけではありません。

// ❌ 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>
  );
}

Reactで「命令的」になってしまう3つのパターン

ReactでよくあるDOMの直接操作パターン図

パターン1:useRef でDOMを直接書き換える

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>
  );
}

パターン2:useEffect 内でDOMを直接操作する

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>
  );
}

パターン3:document.querySelector で要素を取得・変更する

// ❌ 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のメリット・デメリット

宣言的UIのメリットとデメリットの比較図
メリット内容具体例
コードの可読性が高い状態を見れば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 を使った命令的コードを「例外として」書く必要がある

TODOリストで学ぶ:Vanilla JS vs React の実装比較

Vanilla JS(命令的):TODOリスト

<!-- 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(宣言的):TODOリスト

// 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の本質

よくある質問(FAQ)

Vanilla JSをマスターしてからReactを学ぶべきですか?

JavaScriptの基礎はReactを学ぶ前にしっかり理解しておくことを強く推奨します。変数・関数・配列操作(map・filter・spread)・非同期処理(Promise・async/await)を理解した上でReactを学ぶと、「なぜこういう書き方をするのか」が腑に落ちます。特にmap()filter()・スプレッド演算子はReactのコードで頻繁に登場するため必須の前提知識です。

useEffect の中でDOMを操作してはいけませんか?

絶対NGではありませんが、stateで管理できる場合はuseEffect+DOM直接操作を避けるのが原則です。サードパーティライブラリの初期化(Google Maps・Chart.jsなど)やフォーカス制御・スクロールなど「本質的にDOMの操作が必要なケース」は例外として許容されます。判断の目安は「これはstateで表現できるか?」を自問することです。

VueやSvelteも「宣言的UI」ですか?

はい、Vue・Svelte・Angularなども宣言的UIのフレームワークです。いずれも「状態を宣言したらフレームワークがDOMを更新する」というモデルを採用しています。宣言的UIの概念はReact固有ではなく、モダンフロントエンド全体に共通する設計思想です。

宣言的UIはパフォーマンスが悪いですか?

適切に実装すれば、宣言的UIが命令的UIよりパフォーマンスで劣ることはほぼありません。Reactは仮想DOM(Virtual DOM)を使い、変更があった箇所だけ効率的にDOMを更新します。ただし、不要な再レンダリングが多い場合はReact.memouseMemouseCallbackでの最適化が必要になることがあります。

Reactを使っているのにコードが複雑になってきました。なぜですか?

よくあるケースです。コードが複雑になる主な原因として、(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に進みます!


まとめ

  • 本質的な違い:宣言的UIは「フレームワーク」がDOMを更新し、命令的UIは「開発者」がDOMを更新する。
  • よくある誤解:三項演算子を使っても、DOM操作の方法を開発者が書いていれば命令的UI。判断基準は「DOM更新の責任がどこにあるか」。
  • 命令的になってしまうパターン:useRefでDOMを直接書き換える・useEffect内でDOM操作・document.querySelectorを使うの3つを要注意。
  • 命令的が適切なケース:フォーカス制御・スクロール・サードパーティDOMライブラリとの統合は例外として許容される。
  • メリット:コードの可読性向上・バグ削減・テストのしやすさ・状態管理のシンプル化・コンポーネントの再利用性。
  • 前提知識:map・filter・スプレッド演算子・async/awaitなどJavaScriptの基礎が宣言的UIを活かすための土台になる。

React宣言的UIの本質は「状態を変えるだけでUIが追随する」という設計モデルであり、JavaScriptの基礎とセットで理解することで初めてその恩恵を最大限に受けられます


WithCodeを体験できる初級コース公開中!

初級コース(¥49,800)が完全無料に!

  • 期間:1週間
  • 学習内容:ロードマップ/基礎知識/環境構築/HTML/CSS/JavaScript → 宣言的UIを理解してReactを使いこなすための必須のJavaScript基礎を、実践的なカリキュラムで体系的に習得できます

副業・フリーランスが主流になっている今こそ、自らのスキルで稼げる人材を目指してみませんか?

未経験でも心配することはありません。初級コースを受講される方の大多数はプログラミング未経験です。まずは無料カウンセリングで、悩みや不安をお聞かせください!

この記事を書いた人

WithCode(ウィズコード)は「目指すなら稼げる人材」をビジョンに、累計400名以上のフリーランスを輩出してきた超実践型プログラミングスクールです。150社以上の実案件支援を特徴にWeb制作・Webデザインなどの役立つ情報を現場のノウハウに基づいて発信していきます。

– service –WithGroupの運営サービス

  • WithCode
    - ウィズコード -

    スクール

    「未経験」から
    現場で通用する
    スキルを身に付けよう!

    詳細はこちら
  • WithFree
    - ウィズフリ -

    実案件サポート

    制作会社のサポート下で
    実務経験を積んでいこう!

    詳細はこちら
  • WithCareer
    - ウィズキャリ -

    就転職サポート

    大手エージェントのサポート下で
    キャリアアップを目指そう!

    詳細はこちら

公式サイト より
今すぐ
無料カウンセリング
予約!

目次