# TODOアプリ開発カリキュラム (Next.js + TypeScript) ## 概要 このカリキュラムは、JavaScript/TypeScript初学者がNext.js 15 (App Router)を使って実践的なTODOアプリを開発できるようになることを目的としています。 ## 学習目標 - React/Next.jsの基本的な概念を理解する - TypeScriptの型システムに慣れる - 状態管理の基本を学ぶ - CRUDアプリケーションの実装パターンを習得する - モダンなWeb開発のベストプラクティスを身につける ## 前提知識 - HTML/CSSの基礎知識 - JavaScriptの基本文法(変数、関数、配列、オブジェクト) - コマンドラインの基本操作 ## 学習ステップ ### Phase 1: 基礎理解 (2-3時間) #### Step 1.1: プロジェクト構造の理解 - `src/app/`ディレクトリの役割 - `page.tsx`と`layout.tsx`の違い - TypeScriptファイル(.ts/.tsx)の意味 #### Step 1.2: 最初のコンポーネント作成 - Hello Worldコンポーネントの作成 - JSXの基本文法 - 型定義の初歩(stringやnumberの使い方) **💡 詳細な実装手順:** 1. **新しいコンポーネントファイルを作成** ```typescript // src/app/components/HelloWorld.tsx // Reactコンポーネントの基本形 export default function HelloWorld() { return (

Hello World!

Reactコンポーネントの第一歩です

); } ``` **解説:** - `export default function` - このコンポーネントを他のファイルから使えるようにする - JSXは見た目はHTMLに似ているが、実はJavaScriptの拡張構文 - `className` - HTMLのclassの代わりに使用(JavaScriptの予約語と競合を避けるため) 2. **コンポーネントを使ってみる** ```typescript // src/app/page.tsx import HelloWorld from './components/HelloWorld'; export default function Home() { return (

私のTODOアプリ

); } ``` **エラーが出た場合:** - `Cannot find module` → importパスを確認(`./components/HelloWorld`) - `'HelloWorld' is not defined` → importし忘れていないか確認 ### Phase 2: TODOアプリの骨組み作成 (3-4時間) #### Step 2.1: UIコンポーネントの作成 - TODOリストの表示部分 - TODO入力フォーム - Tailwind CSSによるスタイリング #### Step 2.2: 状態管理の導入 - `useState`フックの使い方 - TODOアイテムの型定義 ```typescript type Todo = { id: number; text: string; completed: boolean; }; ``` ### Phase 3: 機能実装 (4-5時間) #### Step 3.1: TODO追加機能 - フォーム送信処理 - 新しいTODOの作成 - リストへの反映 **💡 詳細な実装と解説:** ```typescript // TODO追加関数の実装 const addTodo = () => { // 空の入力をチェック(バリデーション) if (inputValue.trim() === '') { alert('TODOを入力してください'); return; } // 新しいTODOオブジェクトを作成 const newTodo: Todo = { id: Date.now(), // 簡易的なID生成(本番ではuuidを推奨) text: inputValue, completed: false }; // 状態を更新(重要:配列をコピーして新しい配列を作る) setTodos([...todos, newTodo]); // 入力欄をクリア setInputValue(''); }; // フォームで使う場合 const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // ページのリロードを防ぐ addTodo(); }; // JSX部分
setInputValue(e.target.value)} className="flex-1 border p-2 rounded" placeholder="TODOを入力してEnterキー" />
``` **なぜこのように書くのか:** - `trim()` - 空白だけの入力を防ぐ - `Date.now()` - ミリ秒単位のタイムスタンプでIDを生成(簡易版) - `[...todos, newTodo]` - スプレッド構文で既存の配列をコピーして新しい要素を追加 - `e.preventDefault()` - formのデフォルト動作(ページリロード)を止める #### Step 3.2: TODO完了機能 - チェックボックスの実装 - 完了状態の切り替え - 条件付きスタイリング **💡 詳細な実装と解説:** ```typescript // 完了状態を切り替える関数 const toggleTodo = (id: number) => { // mapを使って新しい配列を作成(イミュータビリティ) const updatedTodos = todos.map(todo => { if (todo.id === id) { // 該当するTODOを見つけたら、completedを反転 return { ...todo, completed: !todo.completed }; } return todo; // それ以外はそのまま }); setTodos(updatedTodos); }; // TODOリストの表示部分 ``` **解説:** - `map` - 配列の各要素を変換して新しい配列を作る - `{ ...todo, completed: !todo.completed }` - スプレッド構文でtodoをコピーし、completedだけ上書き - `checked={todo.completed}` - チェックボックスの状態をデータと同期 - `line-through` - 完了時に打ち消し線を表示(Tailwind CSSのクラス) **よくあるミス:** ```typescript // ❌ これは動かない!直接配列を変更している const todo = todos.find(t => t.id === id); todo.completed = !todo.completed; // Reactが変更を検知できない setTodos(todos); // 同じ配列をセットしても再レンダリングされない ``` #### Step 3.3: TODO削除機能 - 削除ボタンの追加 - 配列からの要素削除 - 確認ダイアログ(オプション) **💡 詳細な実装と解説:** ```typescript // 削除関数 const deleteTodo = (id: number) => { // 確認ダイアログを表示(オプション) if (confirm('本当に削除しますか?')) { // filterを使って該当ID以外の要素で新しい配列を作成 const filteredTodos = todos.filter(todo => todo.id !== id); setTodos(filteredTodos); } }; // TODOリストに削除ボタンを追加
  • toggleTodo(todo.id)} className="w-5 h-5 cursor-pointer" /> {todo.text}
  • ``` **解説:** - `filter` - 条件に合う要素だけで新しい配列を作成 - `todo.id !== id` - 削除対象以外の要素を残す - `confirm()` - ブラウザの確認ダイアログ(true/falseを返す) **カスタム確認ダイアログの例:** ```typescript // 状態でモーダルを管理 const [showDeleteModal, setShowDeleteModal] = useState(false); const [deletingId, setDeletingId] = useState(null); // 削除モーダルコンポーネント {showDeleteModal && (

    本当に削除しますか?

    )} ``` ### Phase 4: 高度な機能 (3-4時間) #### Step 4.1: TODO編集機能 - インライン編集の実装 - 保存とキャンセル処理 **💡 詳細な実装と解説:** ```typescript // 編集用の状態管理 const [editingId, setEditingId] = useState(null); const [editText, setEditText] = useState(''); // 編集開始関数 const startEdit = (todo: Todo) => { setEditingId(todo.id); setEditText(todo.text); }; // 編集保存関数 const saveEdit = () => { if (editText.trim() === '') { alert('TODOが空です'); return; } const updatedTodos = todos.map(todo => { if (todo.id === editingId) { return { ...todo, text: editText }; } return todo; }); setTodos(updatedTodos); setEditingId(null); // 編集モードを終了 }; // 編集キャンセル関数 const cancelEdit = () => { setEditingId(null); setEditText(''); }; // TODOリストの表示部分 {todos.map((todo) => (
  • toggleTodo(todo.id)} className="w-5 h-5 cursor-pointer" /> {editingId === todo.id ? ( // 編集モード setEditText(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') saveEdit(); if (e.key === 'Escape') cancelEdit(); }} className="flex-1 border p-1 rounded" autoFocus /> ) : ( // 通常モード startEdit(todo)} className={`flex-1 cursor-pointer ${todo.completed ? 'line-through text-gray-500' : ''}`} > {todo.text} )} {editingId === todo.id ? ( // 編集モードのボタン
    ) : ( // 通常モードのボタン )}
  • ))} ``` **ポイント:** - `editingId` - 現在編集中のTODOのIDを保持 - `onKeyDown` - キーボードイベントでEnter/Escを検知 - `autoFocus` - 編集開始時に自動的にフォーカス - 三項演算子 `? :` - 条件によって表示を切り替え #### Step 4.2: フィルタリング機能 - All/Active/Completedの切り替え - 条件付きレンダリング #### Step 4.3: データの永続化 - Local Storageの活用 - `useEffect`フックの理解 ### Phase 5: 仕上げとベストプラクティス (2-3時間) #### Step 5.1: コンポーネントの分割 - 再利用可能なコンポーネント化 - propsの型定義 #### Step 5.2: エラーハンドリング - 入力検証 - エラーメッセージの表示 #### Step 5.3: パフォーマンス最適化 - `useCallback`の基礎 - キー属性の重要性 ## 各ステップの実装詳細 ### Step 1.1 実装例 ```typescript // src/app/page.tsx export default function Home() { return (

    私のTODOアプリ

    ); } ``` ### Step 2.2 実装例 ```typescript // TODOの状態管理 const [todos, setTodos] = useState([]); ``` **💡 完全な実装例とコード解説:** ```typescript // src/app/page.tsx 'use client'; // ← 重要!状態管理を使う時は必須 import { useState } from 'react'; // TypeScriptの型定義 // interfaceまたはtypeで定義可能 type Todo = { id: number; // 一意の識別子 text: string; // TODOの内容 completed: boolean; // 完了状態 }; export default function Home() { // useState フックの使い方 // const [状態変数, 状態を更新する関数] = useState<型>(初期値); const [todos, setTodos] = useState([]); // 入力フォームの状態も管理 const [inputValue, setInputValue] = useState(''); return (

    TODOアプリ

    {/* 入力フォーム */}
    setInputValue(e.target.value)} className="border p-2 rounded" placeholder="TODOを入力" />
    {/* TODOリスト表示 */}
      {todos.map((todo) => (
    • {todo.text}
    • ))}
    ); } ``` **解説:** - `'use client'` - クライアントサイドで実行するコンポーネントであることを宣言 - `useState([])` - 空の配列で初期化、型はTodoの配列 - `onChange` - 入力が変更されるたびに実行される - `map` - 配列の各要素をJSXに変換 - `key={todo.id}` - Reactが効率的に更新するために必要 ## つまずきやすいポイントと対策 ### 1. TypeScriptのエラー - 型定義が分からない → 最初はanyを使い、後から具体的な型に修正 - 型推論に頼る → VSCodeの補完機能を活用 **💡 よくあるTypeScriptエラーと解決策:** ```typescript // エラー例1: Parameter 'e' implicitly has an 'any' type // ❌ エラーが出るコード const handleChange = (e) => { setInputValue(e.target.value); }; // ✅ 解決策1: 型を明示的に指定 const handleChange = (e: React.ChangeEvent) => { setInputValue(e.target.value); }; // ✅ 解決策2: インラインで書く(型推論が効く) setInputValue(e.target.value)} /> // エラー例2: Object is possibly 'undefined' // ❌ エラーが出るコード const todo = todos.find(t => t.id === id); console.log(todo.text); // todoがundefinedの可能性 // ✅ 解決策: オプショナルチェイニング console.log(todo?.text); // または条件分岐 if (todo) { console.log(todo.text); } ``` **VSCodeの便利機能:** - 変数にカーソルを合わせると型が表示される - Ctrl+Space で補完候補を表示 - エラーの上にカーソルを置くと「Quick Fix」が提案される ### 2. 状態更新のイミュータビリティ - 配列の直接変更はNG → スプレッド構文やfilter/mapを使用 ```typescript // NG todos.push(newTodo); // OK setTodos([...todos, newTodo]); ``` ### 3. イベントハンドラーの型 ```typescript const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); // 処理 }; ``` ## 評価基準 ### 基本機能 (必須) - [ ] TODOの追加ができる - [ ] TODOの完了/未完了の切り替えができる - [ ] TODOの削除ができる - [ ] 適切な型定義がされている ### 追加機能 (推奨) - [ ] TODOの編集ができる - [ ] フィルタリング機能がある - [ ] データが永続化される - [ ] レスポンシブデザイン ### コード品質 - [ ] コンポーネントが適切に分割されている - [ ] エラーハンドリングがされている - [ ] コードが読みやすい ## 発展課題 1. **ドラッグ&ドロップ**での並び替え 2. **カテゴリー機能**の追加 3. **期限設定**機能 4. **Next.js API Routes**を使ったバックエンド実装 5. **認証機能**の追加(NextAuth.js) ## 推奨リソース ### 📚 公式ドキュメント - [Next.js公式ドキュメント](https://nextjs.org/docs) - [TypeScript Handbook](https://www.typescriptlang.org/docs/handbook/intro.html) - [React公式チュートリアル](https://react.dev/learn) - [Tailwind CSS公式ドキュメント](https://tailwindcss.com/docs) ### 🎥 日本語の動画教材 - [【2024年最新】React完全入門ガイド|Hooks、Next.js、TypeScriptまで](https://www.youtube.com/watch?v=TGiCr7S2E7E) - [Next.js入門 - App Routerを基礎から学ぶ](https://www.youtube.com/watch?v=BVq8n8tVNJg) - [TypeScript入門 完全版](https://www.youtube.com/watch?v=F9vzRz6jyRk) ### 📖 日本語の参考記事・書籍 - [サバイバルTypeScript](https://typescriptbook.jp/) - TypeScriptの実践的な解説 - [React入門 TODOアプリ開発](https://zenn.dev/topics/react-todo) - Zennの関連記事集 - [Next.js App Router入門](https://zenn.dev/hayato94087/articles/e9712c4ab7a8f1) - 詳細な解説記事 - 書籍:「React実践入門」(技術評論社) - 書籍:「TypeScript実践プログラミング」(マイナビ出版) ### 🛠️ 実践的な学習リソース - [React Tutorial: Tic-Tac-Toe(日本語版)](https://ja.react.dev/learn/tutorial-tic-tac-toe) - 公式チュートリアル - [TypeScript Playground](https://www.typescriptlang.org/play) - ブラウザで試せる - [StackBlitz](https://stackblitz.com/) - オンラインでNext.jsを試せる ## サポート方法 1. **ペアプログラミング**: 詰まったらすぐに質問 2. **コードレビュー**: 各Phaseごとにレビュー 3. **デバッグセッション**: エラーの読み方を一緒に学習 4. **ベストプラクティス共有**: より良い書き方を随時提案 --- このカリキュラムは柔軟に調整可能です。学習者のペースに合わせて進めてください。 ## 👨‍🏫 学習の進め方アドバイス ### 初心者がつまずきやすいポイント 1. **エラーメッセージを恐れない** - エラーは学習のチャンス - エラーメッセージをしっかり読んでコピー&ペーストでGoogle検索 2. **小さく始める** - いきなり全部を理解しようとしない - まず動くものを作ってから理解を深める 3. **コードを写経する** - 最初はサンプルコードをそのまま写す - 動いたら少しずつ変更して実験 ### 各Phaseの確認ポイント #### Phase 1 終了時 - [ ] `npm run dev`でアプリが起動する - [ ] ブラウザに「Hello World」が表示される - [ ] コンポーネントを自分で作成できる #### Phase 2 終了時 - [ ] TODOの入力フォームが表示される - [ ] TODOリストが表示される(まだ空でOK) - [ ] スタイルが適用されている #### Phase 3 終了時 - [ ] TODOを追加できる - [ ] TODOを完了/未完了に切り替えられる - [ ] TODOを削除できる ## 🔥 FAQ(よくある質問) ### Q: `'use client'`を忘れてエラーが出ました **A:** useStateなどのフックを使う時は、ファイルの先頭に`'use client'`が必要です。 ### Q: 「Cannot find module」エラーが出ます **A:** importのパスが間違っている可能性があります。`./`で始まる相対パスか、`@/`で始まるエイリアスを確認してください。 ### Q: スタイルが適用されません **A:** classNameを使っているか確認してください(classではなく)。また、Tailwind CSSのクラス名が正しいか公式ドキュメントで確認しましょう。 ### Q: TODOが保存されずリロードすると消えます **A:** Phase 4のLocal Storage実装まではこれが正常です。メモリ上にしかデータが保存されていません。 ### Q: TypeScriptの型エラーが難しいです **A:** 最初は`any`型を使ってもOKです。動くようになったら徐々に適切な型に置き換えていきましょう。 ### Q: mapやfilterがよく分かりません **A:** これらはJavaScriptの配列メソッドです。MDNの日本語ドキュメントで詳しく解説されています。 - [Array.prototype.map()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map) - [Array.prototype.filter()](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)