第3部:実践チュートリアル Step 15 / 20

Todoアプリ - フロントエンド編

Next.jsを使って、Todoアプリの画面を作成します。まずはモックデータで動く状態を作り、次のページでAPIと連携させます。

このページで学ぶこと

📝

TypeScriptの型定義

データの形を決める

🧩

Reactコンポーネント

UIの部品を組み立てる

🔄

useStateで状態管理

データの変化に応じてUIを更新

完成イメージ

Todoリスト

  • 買い物に行く
  • 部屋を掃除する

Step 1: Todoの型定義を作成

AIへの指示

frontend/src/types/todo.ts を作成して、
Todoの型定義を追加してください。

Todo型:
- id: number
- title: string
- completed: boolean

完成コード: src/types/todo.ts

export interface Todo {
  id: number;
  title: string;
  completed: boolean;
}

なぜ型定義を作る? TypeScriptの型定義は「データの設計図」です。Todoが必ず id・title・completed を持つことを宣言しておくと、コードを書くときにスペルミスや入れ忘れをエディタが自動で検出してくれます。

Step 2: Todoリストコンポーネントを作成

AIへの指示

frontend/src/app/page.tsx を編集して、
Todoリストアプリを作成してください。

機能:
1. Todoの一覧表示
2. 新しいTodoの追加フォーム
3. Todoの完了/未完了の切り替え(チェックボックス)
4. Todoの削除ボタン
5. 完了したTodoは取り消し線を表示

まずはモックデータで動くようにしてください。
APIとの連携は後で行います。

使用技術:
- useState で状態管理
- Tailwind CSS でスタイリング

完成コード例: src/app/page.tsx

"use client";

import { useState } from "react";
import { Todo } from "@/types/todo";

// モックデータ(仮のデータ)
const initialTodos: Todo[] = [
  { id: 1, title: "買い物に行く", completed: false },
  { id: 2, title: "部屋を掃除する", completed: true },
];

export default function Home() {
  const [todos, setTodos] = useState<Todo[]>(initialTodos);
  const [newTitle, setNewTitle] = useState("");

  // Todo追加
  const addTodo = () => {
    if (!newTitle.trim()) return;
    const newTodo: Todo = {
      id: Date.now(),
      title: newTitle,
      completed: false,
    };
    setTodos([...todos, newTodo]);
    setNewTitle("");
  };

  // 完了切り替え
  const toggleTodo = (id: number) => {
    setTodos(todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    ));
  };

  // 削除
  const deleteTodo = (id: number) => {
    setTodos(todos.filter(todo => todo.id !== id));
  };

  return (
    <main className="min-h-screen p-8 max-w-md mx-auto">
      <h1 className="text-2xl font-bold mb-6 text-center">
        Todoリスト
      </h1>

      {/* 追加フォーム */}
      <div className="flex mb-6">
        <input
          type="text"
          value={newTitle}
          onChange={(e) => setNewTitle(e.target.value)}
          onKeyDown={(e) => e.key === "Enter" && addTodo()}
          placeholder="新しいTodoを入力..."
          className="flex-1 border rounded-l-lg px-4 py-2"
        />
        <button
          onClick={addTodo}
          className="bg-blue-500 text-white px-4 py-2
            rounded-r-lg hover:bg-blue-600"
        >
          追加
        </button>
      </div>

      {/* Todoリスト */}
      <ul className="space-y-2">
        {todos.map((todo) => (
          <li
            key={todo.id}
            className="flex items-center justify-between
              p-3 bg-gray-50 rounded"
          >
            <div className="flex items-center">
              <input
                type="checkbox"
                checked={todo.completed}
                onChange={() => toggleTodo(todo.id)}
                className="mr-3"
              />
              <span
                className={todo.completed ?
                  "line-through text-gray-400" : ""}
              >
                {todo.title}
              </span>
            </div>
            <button
              onClick={() => deleteTodo(todo.id)}
              className="text-red-500 hover:text-red-700"
            >
              削除
            </button>
          </li>
        ))}
      </ul>
    </main>
  );
}

コードの解説

"use client" とは?

Next.jsのApp Routerでは、デフォルトでコンポーネントはサーバー側で実行されます(Server Component)。しかし useStateonClick のようなブラウザ上の操作が必要な場合は、ファイルの先頭に "use client" と書いて「クライアントコンポーネント」にする必要があります。

覚え方:ユーザーが操作するもの(ボタンクリック、入力フォーム、状態の変更)があるページには "use client" が必要。

useState で状態管理

このコードでは2つの状態を管理しています:

変数 初期値 役割
todos initialTodos(モックデータ) Todoの一覧データを保持
newTitle ""(空文字) 入力フォームの現在の値

setTodossetNewTitle を呼ぶと値が更新され、画面が自動的に再描画されます。

3つの操作関数

addTodo(追加)

[...todos, newTodo] で既存配列に新しいTodoを追加。...todos は「スプレッド構文」と呼ばれ、配列をコピーして展開します。

toggleTodo(完了切り替え)

todos.map() で配列をループし、クリックしたTodoの completed を反転。{ ...todo, completed: !todo.completed } はオブジェクトの一部だけ書き換えるテクニックです。

deleteTodo(削除)

todos.filter() で条件に合わないもの(=削除対象)を除外した新しい配列を作成。Reactでは配列を直接変更せず、新しい配列を作って置き換えます。

JSXの描画部分

key

key={todo.id} — Reactがリストの各要素を効率的に更新するための目印。keyがないと警告が出ます。

条件付きclass

className={todo.completed ? "line-through ..." : ""} — 完了状態に応じてスタイルを切り替え。三項演算子(条件 ? A : B)を使っています。

Enter対応

onKeyDown={(e) => e.key === "Enter" && addTodo()} — Enterキーでも追加できるようにするイベントハンドラ。

Step 3: 動作確認

  1. フロントエンドサーバーを起動: npm run dev
  2. ブラウザで http://localhost:3000 を開く
  3. 以下を確認:
    • Todoが一覧表示されている
    • 新しいTodoを追加できる(ボタンクリック&Enterキー両方)
    • チェックボックスで完了/未完了を切り替えられる(取り消し線が出る)
    • 削除ボタンで削除できる
    • 空欄のまま「追加」を押しても何も起きない(バリデーション)

注意:この時点ではデータはメモリ上にあるため、ページをリロードすると初期状態に戻ります。次のページでAPIと連携して永続化します。

うまく動かないときは

  • 画面が真っ白:ブラウザのDevTools(F12)→ Console タブでエラーを確認。Claude Codeにエラーメッセージを伝えましょう。
  • "use client" エラー:ファイルの1行目に "use client"; があるか確認。
  • 型エラー:Todo型のインポートパスが @/types/todo になっているか確認。
前へ:バックエンド編 次へ:連携編