JavaScript: 総合プロジェクト:Todo アプリ
最後のレッスンにおめでとうございます!これまでの27レッスンで、変数、関数、配列、DOM、イベント、JSON、非同期プログラミング、クラスなどを学んできました。それらをすべて結びつけて、完全な小さなアプリケーションを構築する時が来ました。このプロジェクトは、学んだすべての集大成となります。
1. プロジェクト概要
Todo アプリを構築します。シンプルに見えますが、学んだほぼすべてのコアコンCEPTに触れます。完成すれば、実際に使える生産性ツールが手に入ります。
▶ サンプル
JAVASCRIPT
// セクションのコード例
const message = "Hello, World!";
console.log(message);
これは最もシンプルな例で、後のセクションで詳しく説明します。## 2. 機能要件
- Todo アイテムの追加 — 入力フィールド + ボタン。Enter キーまたはクリックで新しいアイテムを追加
- 完了/未完了のトグル — Todo をクリックして完了状態を切り替え(取り消し線効果)
- Todo アイテムの削除 — 各アイテムに削除ボタンが付いている
- フィルタリング — 3つのボタン:すべて / 未完了 / 完了済み
- ローカルストレージ —
localStorageを使ってページ更新後もデータを永続化 - 残りアイテム数の表示 — 下部に「X 件残っています」と表示
2. 技術要件
| コンCEPT | 使用箇所 |
|---|---|
| クラス | TodoApp クラスですべてのロジックを整理 |
| async/await | localStorage の読み書きを非同期関数でラップ |
| DOM 操作 | リスト項目の動的作成、コンテンツ更新、要素の削除 |
| イベント処理 | フォーム submit、リスト click、フィルタボタン click |
| JSON | JSON.stringify で保存、JSON.parse で読み込み |
| 配列メソッド | filter、map、find で Todo データを管理 |
| テンプレートリテラル | HTML 文字列の構築 |
3. 受入基準
- [ ] 6つの機能すべてが正しく動作する
- [ ] ページ更新後もデータが保持される
- [ ] コードに明確なコメントがある
- [ ]
alert/prompt/confirmを使わず、ページ内のインタラクションを使用 - [ ] 空の入力は拒否される(メッセージ付き)
4. 参考実装アイデア
以下はヒントと方向性です。完全なコードではありません。自分で書くことで本当の学びが得られます。
(1) データ構造
HTML
<script>
// 各 Todo アイテムはオブジェクト
const todo = {
id: Date.now(), // ユニーク識別子
text: 'JavaScript を学ぶ',
completed: false
};
console.log(todo);
// すべての Todo アイテムは配列に格納
let todos = [];
</script>
(2) クラス構造
HTML
<script>
class TodoApp {
constructor() {
this.todos = []; // データ
this.filter = 'all'; // 現在のフィルタ
this.init(); // 初期化
}
async init() {
// 1. localStorage からデータを読み込み
// 2. イベントをバインド
// 3. リストをレンダリング
}
async save() {
// JSON.stringify → localStorage.setItem
}
async load() {
// localStorage.getItem → JSON.parse
}
addTodo(text) { /* オブジェクト作成、push、保存、レンダリング */ }
toggleTodo(id) { /* アイテムを検索、completed を反転、保存、レンダリング */ }
deleteTodo(id) { /* アイテムをフィルタアウト、保存、レンダリング */ }
render() {
// 1. this.filter に基づいて todos をフィルタ
// 2. リストコンテナをクリア
// 3. フィルタされた配列をループし、DOM ノードを作成
// 4. カウント表示を更新
}
}
</script>
(3) イベントバインド戦略
- フォーム
submit:e.preventDefault()→ 入力値を取得 →addTodo(text)→ 入力をクリア - リスト
click(イベント委譲):e.targetがトグルまたは削除ボタンかを確認 → 適切なメソッドを呼び出し - フィルタボタン
click:this.filterを更新 →render()
(4) レンダリング戦略
HTML
<script>
render() {
const filtered = this.todos.filter(function(todo) {
if (this.filter === 'active') return !todo.completed;
if (this.filter === 'completed') return todo.completed;
return true; // 'all'
}.bind(this));
// コンテナをクリア
this.listEl.innerHTML = '';
filtered.forEach(function(todo) {
var li = document.createElement('li');
// コンテンツ、スタイル、データ属性を設定
// トグルと削除イベントをバインド
this.listEl.appendChild(li);
}.bind(this));
// カウントを更新
var activeCount = this.todos.filter(function(t) { return !t.completed; }).length;
this.countEl.textContent = activeCount + ' 件残っています';
}
</script>
5. 拡張アイデア
基本機能が動作したら、これらの高度なチャレンジに挑戦してみましょう。
- 🔄 ドラッグ&ドロップソート — HTML5 Drag & Drop API またはタッチイベントで実装
- ✏️ 編集 — Todo をダブルクリックして編集モードに入り、Enter で保存
- 🏷️ カテゴリタグ — ラベル(仕事 / 学習 / 生活)を追加し、タグでフィルタ
- 🎨 テーマ切り替え — ダーク/ライトテーマ、
localStorageで永続化 - ⏰ 期限リマインダー — 各アイテムに締め切りを追加、期限超過を赤色でハイライト
6. 課題
上記の Todo アプリをすべての機能を実装し、すべての受入基準をクリアしてください。
推奨ステップ:
- HTML 構造と CSS スタイルから始める
TodoAppクラスのスケルトンを構築(constructor、init)addTodoとrenderを実装。まず追加を動作させるtoggleTodoを実装。完了/未完了の切り替えdeleteTodoを実装。アイテムの削除- フィルタリングを実装
saveとloadを実装。データの永続化- 残りアイテム数の表示
- 最終仕上げ:空入力のバリデーション、スタイルの調整
💡 ヒント: 一度にすべてを構築しようとしないでください。まず最小のものを動作させ、その後1つずつ追加していきましょう。ソフトウェア開発は「動かして、正しくして、速くする」です。
▶ サンプル
クラスと localStorage を使った完全な Todo アプリ:
HTML
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; max-width: 600px; margin: 20px auto; }
.done { text-decoration: line-through; color: gray; }
</style>
</head>
<body>
<h1>Todo アプリ</h1>
<input id="taskInput" placeholder="新しいタスク">
<button id="addBtn">追加</button>
<ul id="taskList"></ul>
<script>
class Todo {
constructor() { this.tasks = JSON.parse(localStorage.getItem('tasks') || '[]'); }
add(text) { this.tasks.push({text, done: false}); localStorage.setItem('tasks', JSON.stringify(this.tasks)); this.render(); }
toggle(i) { this.tasks[i].done = !this.tasks[i].done; localStorage.setItem('tasks', JSON.stringify(this.tasks)); this.render(); }
render() { document.getElementById('taskList').innerHTML = this.tasks.map((t, i) => `<li class="${t.done ? 'done' : ''}">${t.text}</li>`).join(''); }
}
const todo = new Todo();
document.getElementById('addBtn').onclick = () => todo.add(document.getElementById('taskInput').value);
todo.render();
</script>
</body>
</html>
クラスベースの状態管理、localStorage 永続化、動的 DOM レンダリングを示す完全な Todo アプリです。
❓ よくある質問
Q Classを使う理由は?
A Class構文はより明確で、
constructor、this、継承が明確にサポートされています。内部はプロトタイプベースですが、オブジェクト指向言語のように書け、可読性が向上します。Q localStorageのデータは失われますか?
A ユーザーが手動でブラウザデータを消去するかプライベートブラウジングを使用しない限り、localStorageのデータは永続的に保存されます。文字列しか保存できないため、オブジェクトは
JSON.stringify が必要です。Q イベントデリゲーションとは?Todoアプリでなぜ便利?
A 親要素にイベントリスナーをバインドし、
e.target で子要素のイベントを処理する手法です。新しいタスク項目が自動的にイベント処理を得られ、再バインド不要でパフォーマンスも向上します。📖 まとめ
- このプロジェクトは変数、関数、配列、オブジェクト、DOM操作、イベント、JSON、Classの知識を統合
- Classでコードを整理。
constructorで初期化、メソッドはプロトタイプに - localStorageでデータ永続化、ページ再読込後もデータ保持
- イベントデリゲーションはイベントバブリングを活用、重複バインドを削減
- フィルタリングは
filter状態と条件付きレンダリングで実現 — MVVMパターンの基礎
📝 練習問題
- 基礎:コア Todo 機能を完成させてください。追加、完了トグル、削除を配列を使って実装(
localStorageは不要)。 - 中級:基本版に
localStorageによる永続化とフィルタリング(すべて / 未完了 / 完了済み)を追加してください。ページ更新後もデータが保持されるようにしてください。 - チャレンジ:拡張機能を少なくとも1つ実装してください(ドラッグ&ドロップソート、編集、カテゴリタグ、テーマ切り替え、または期限リマインダー)。アプローチの簡単な説明も書いてください。