0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[JavaScript][React] 動的UIを持つFEアプリケーションの基本構造

Last updated at Posted at 2025-10-19

概要

本記事では、動的にUIが変化するフロントエンドアプリケーション(FEアプリ) の基本構造を
Vanilla JavaScript(命令的構造)React(宣言的構造) の両視点から比較・整理します。

具体的には、TODOアプリを例にして、
「UIの生成・イベント処理・状態管理」をどのように構成するかを段階的に理解できる構成となっています。

目次

基本構造

Vanilla JS(命令的構造)

1. import / 初期設定セクション
2. DOM参照キャッシュ / 定数セクション
3. 関数定義セクション
|  ├─ 3.1 データアクセス関数セクション(Storage I/O)
|  ├─ 3.2 データ操作(ビジネスロジック)関数セクション(Model更新系)
|  ├─ 3.3 イベントハンドラー関数セクション
|  └─ 3.4 UI更新関数セクション(副作用を内包)
|        ├─ 3.4.1 要素生成・属性設定
|        ├─ 3.4.2 イベント付与(click, inputなど)
|        ├─ 3.4.3 構造組み立て(appendChildでネスト構築)
|        └─ 3.4.4 DOM挿入(append/removeで画面反映)
4. 初期化 / イベントハンドラー登録セクション

Vanilla JSでは、手続き的(命令的) にDOMを直接操作します。
createElement()appendChild()addEventListener() などを組み合わせ、
UIを構築・更新する流れを「副作用として明示的に」記述します。

React版(宣言的構造)

1. importセクション
2. 型定義セクション(TypeScript使用時)
3. 関数定義セクション
   ├─ 3.1 内部状態管理セクション
   ├─ 3.2 イベントハンドラーセクション
   ├─ 3.3 副作用処理セクション
   └─ 3.4 返り値構築・ロジックセクション

Reactでは、宣言的(Declarative) にUIを記述します。
DOMを直接触る代わりに、useState で状態を保持し、
状態変化に応じてReactが仮想DOMを再構築します。

この構造により、UIの生成・更新・削除をReactが自動で最適化します。

コード例

Vanilla JS(命令的構造)

// ==============================
// 1. import / 初期設定セクション
// ==============================
import "./styles.css";

// ==============================
// 2. DOM参照キャッシュ / 定数セクション
// ==============================
// ※今回は必要最低限のため未使用(関数内で直接参照)
// const inputEl = document.getElementById("add-text");
// const addBtn  = document.getElementById("add-button");
// const incList = document.getElementById("incomplete-list");
// const compList = document.getElementById("complete-list");

// ==============================
// 3. 関数定義セクション
// ==============================

// ------------------------------
// 3.1 データアクセス関数セクション(Storage I/O)
// ------------------------------

// ------------------------------
// 3.2 データ操作(ビジネスロジック)関数セクション(Model更新系)
// ------------------------------

// ------------------------------
// 3.3 イベントハンドラー関数セクション
// ------------------------------
const onClickAdd = () => {
  // テキストボックスの値を取得し、初期化する(副作用)
  const inputText = document.getElementById("add-text").value;
  document.getElementById("add-text").value = "";

  // 未完了リストに追加(UI更新関数呼び出し)
  createIncompleteTodo(inputText);
};

// ------------------------------
// 3.4 UI更新関数セクション(副作用を内包)
// ------------------------------
const createIncompleteTodo = (todo) => {
  // ------------------------------
  // 3.4.1 要素生成・属性設定
  // ------------------------------
  const li = document.createElement("li");
  const div = document.createElement("div");
  div.className = "list-row";

  const p = document.createElement("p");
  p.className = "todo-item";
  p.innerText = todo;

  const completeButton = document.createElement("button");
  completeButton.innerText = "完了";

  const deleteButton = document.createElement("button");
  deleteButton.innerText = "削除";

  // ------------------------------
  // 3.4.2 イベント付与(click, inputなど)
  // ------------------------------
  completeButton.addEventListener("click", () => {
    const moveTarget = completeButton.closest("li");
    completeButton.nextElementSibling.remove();
    completeButton.remove();

    const backButton = document.createElement("button");
    backButton.innerText = "戻す";

    backButton.addEventListener("click", () => {
      const todoText = backButton.previousElementSibling.innerText;
      createIncompleteTodo(todoText);
      backButton.closest("li").remove();
    });

    moveTarget.firstElementChild.appendChild(backButton);
    document.getElementById("complete-list").appendChild(moveTarget);
  });

  deleteButton.addEventListener("click", () => {
    const deleteTarget = deleteButton.closest("li");
    document.getElementById("incomplete-list").removeChild(deleteTarget);
  });

  // ------------------------------
  // 3.4.3 構造組み立て(appendChildでネスト構築)
  // ------------------------------
  div.appendChild(p);
  div.appendChild(completeButton);
  div.appendChild(deleteButton);
  li.appendChild(div);

  // ------------------------------
  // 3.4.4 DOM挿入(append/removeで画面反映)
  // ------------------------------
  document.getElementById("incomplete-list").appendChild(li);
};

// ==============================
// 4. 初期化 / イベントハンドラー登録セクション
// ==============================
document
  .getElementById("add-button")
  .addEventListener("click", onClickAdd);

React版(宣言的構造)

// ==============================
// 1. importセクション
// ==============================
import { useState } from "react";
import "./styles.css";

// ==============================
// 2. 型定義セクション(TypeScript使用時)
// ==============================
// ※今回は使用なし
// type Todo = string;
// type Props = { title: string };

// ==============================
// 3. 関数定義セクション(Appコンポーネント定義)
// ==============================
export default function App() {
  // ------------------------------
  // 3.1 内部状態管理セクション(useState)
  // ------------------------------
  const [inputText, setInputText] = useState("");
  const [incompleteTodos, setIncompleteTodos] = useState<string[]>([]);
  const [completeTodos, setCompleteTodos] = useState<string[]>([]);

  // ------------------------------
  // 3.2 イベントハンドラーセクション
  // ------------------------------
  const onChangeText = (e: React.ChangeEvent<HTMLInputElement>) => {
    setInputText(e.target.value);
  };
  const onClickAdd = () => {
    if (inputText === "") return;
    setIncompleteTodos([...incompleteTodos, inputText]);
    setInputText("");
  };
  const onClickComplete = (index: number) => {
    const newIncomplete = [...incompleteTodos];
    const doneTodo = newIncomplete.splice(index, 1)[0];
    setIncompleteTodos(newIncomplete);
    setCompleteTodos([...completeTodos, doneTodo]);
  };
  const onClickDelete = (index: number) => {
    const newIncomplete = [...incompleteTodos];
    newIncomplete.splice(index, 1);
    setIncompleteTodos(newIncomplete);
  };
  const onClickBack = (index: number) => {
    const newComplete = [...completeTodos];
    const backTodo = newComplete.splice(index, 1)[0];
    setCompleteTodos(newComplete);
    setIncompleteTodos([...incompleteTodos, backTodo]);
  };

  // ------------------------------
  // 3.3 副作用処理セクション(useEffectなど)
  // ------------------------------
  // ※今回は外部通信やライフサイクル処理がないため未使用

  // ------------------------------
  // 3.4 返り値構築・ロジックセクション(JSX構築)
  // ------------------------------
  return (
    <div className="App">
      <div className="input-area">
        <input
          id="add-text"
          placeholder="TODOを入力"
          value={inputText}
          onChange={onChangeText}
        />
        <button id="add-button" onClick={onClickAdd}>
          追加
        </button>
      </div>

      <div className="incomplete-area">
        <h2>未完了のTODO</h2>
        <ul id="incomplete-list">
          {incompleteTodos.map((todo, index) => (
            <li key={index}>
              <div className="list-row">
                <p className="todo-item">{todo}</p>
                <button onClick={() => onClickComplete(index)}>完了</button>
                <button onClick={() => onClickDelete(index)}>削除</button>
              </div>
            </li>
          ))}
        </ul>
      </div>

      <div className="complete-area">
        <h2>完了したTODO</h2>
        <ul id="complete-list">
          {completeTodos.map((todo, index) => (
            <li key={index}>
              <div className="list-row">
                <p className="todo-item">{todo}</p>
                <button onClick={() => onClickBack(index)}>戻す</button>
              </div>
            </li>
          ))}
        </ul>
      </div>
    </div>
  );
}

命令的構造 vs 宣言的構造

観点 Vanilla JS(命令的) React(宣言的)
DOM操作 createElement, appendChild など明示的 Reactが仮想DOMを自動制御
状態管理 DOM・変数の直接操作 useState で一元管理
再描画 手動でDOM更新 状態変化時に自動再描画
コード量 手続き的で冗長 構造的で整理されやすい
学習目的 DOM構造・イベント理解に最適 状態管理・UI設計の習得に最適

まとめ

  • Vanilla JSでは、UIを「どう作るか」(手続き)を記述する。
  • Reactでは、UIが「どうあるべきか」(宣言)を記述する。
  • どちらもUIを動的に変化させるためのアプローチだが、
    Reactは副作用を抽象化し、より明確に「状態駆動型UI」を実現する。

参考リンク

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?