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)。しかし useState や onClick のようなブラウザ上の操作が必要な場合は、ファイルの先頭に "use client" と書いて「クライアントコンポーネント」にする必要があります。
覚え方:ユーザーが操作するもの(ボタンクリック、入力フォーム、状態の変更)があるページには "use client" が必要。
useState で状態管理
このコードでは2つの状態を管理しています:
| 変数 | 初期値 | 役割 |
|---|---|---|
todos |
initialTodos(モックデータ) | Todoの一覧データを保持 |
newTitle |
""(空文字) | 入力フォームの現在の値 |
setTodos や setNewTitle を呼ぶと値が更新され、画面が自動的に再描画されます。
3つの操作関数
addTodo(追加)
[...todos, newTodo] で既存配列に新しいTodoを追加。...todos は「スプレッド構文」と呼ばれ、配列をコピーして展開します。
toggleTodo(完了切り替え)
todos.map() で配列をループし、クリックしたTodoの completed を反転。{ ...todo, completed: !todo.completed } はオブジェクトの一部だけ書き換えるテクニックです。
deleteTodo(削除)
todos.filter() で条件に合わないもの(=削除対象)を除外した新しい配列を作成。Reactでは配列を直接変更せず、新しい配列を作って置き換えます。
JSXの描画部分
key={todo.id} — Reactがリストの各要素を効率的に更新するための目印。keyがないと警告が出ます。
className={todo.completed ? "line-through ..." : ""} — 完了状態に応じてスタイルを切り替え。三項演算子(条件 ? A : B)を使っています。
onKeyDown={(e) => e.key === "Enter" && addTodo()} — Enterキーでも追加できるようにするイベントハンドラ。
Step 3: 動作確認
- フロントエンドサーバーを起動:
npm run dev - ブラウザで http://localhost:3000 を開く
- 以下を確認:
- Todoが一覧表示されている
- 新しいTodoを追加できる(ボタンクリック&Enterキー両方)
- チェックボックスで完了/未完了を切り替えられる(取り消し線が出る)
- 削除ボタンで削除できる
- 空欄のまま「追加」を押しても何も起きない(バリデーション)
注意:この時点ではデータはメモリ上にあるため、ページをリロードすると初期状態に戻ります。次のページでAPIと連携して永続化します。
うまく動かないときは
- 画面が真っ白:ブラウザのDevTools(F12)→ Console タブでエラーを確認。Claude Codeにエラーメッセージを伝えましょう。
- "use client" エラー:ファイルの1行目に
"use client";があるか確認。 - 型エラー:Todo型のインポートパスが
@/types/todoになっているか確認。