2日目のゴールと今日のテーマ
2日目は「ToDoアプリを“ちゃんと動くもの”に近づける日」です。
1日目で作ったのは、あくまで「設計の土台」と「表示の骨組み」でした。
今日はここまで持っていきます。
- タスクを追加できる
- タスクを完了/未完了に切り替えられる
- タスクを削除できる
- フィルタ(全件/未完了/完了)がちゃんと効く
そして、その裏側で大事なのは、
- 状態(state)をどう更新するか
- 配列+オブジェクトのデータ構造をどう扱うか
- 再描画(render)をどうシンプルに保つか
この 3 つです。
状態管理の復習と、今日の拡張ポイント
1日目の state をもう一度確認する
昨日の時点で、state はこんな形でした。
const state = {
tasks: [],
filter: "all", // "all" | "active" | "completed"
};
JavaScriptそして、タスクはこういうオブジェクトでした。
{
id: 1,
title: "牛乳を買う",
done: false,
}
JavaScript今日やることは、この tasks 配列に対して
- 追加(push)
- 完了状態の切り替え(true / false の変更)
- 削除(配列から取り除く)
を、きれいな形で実装することです。
ここで大事なのは、「どのタスクを操作するか」を
id で特定するという考え方です。
完了切り替えの設計:id でタスクを探して done を反転する
なぜ id が必要なのか
画面上にはタスクが縦に並んでいますが、
「上から何番目か」で判断すると、フィルタや削除でズレてしまいます。
だから、タスクごとに一意な id を持たせておき、
「この id のタスクを操作する」という形にします。
完了切り替え関数を作る
まずは、state を更新する関数から作ります。
function toggleTaskDone(id) {
const task = state.tasks.find((t) => t.id === id);
if (!task) return;
task.done = !task.done;
render();
}
JavaScriptここでのポイントは二つです。
一つ目は、find で「id が一致するタスク」を探していること。
配列のインデックスではなく、id で探すことで、
順番が変わっても正しく動きます。
二つ目は、「状態を変えたら必ず render を呼ぶ」というルール。
画面は state の“結果”なので、state を変えたら再描画が必要です。
削除の設計:filter で「残すものだけ」を残す
削除は「消す」ではなく「残すものを選ぶ」
タスク削除は、こう考えるとシンプルです。
- 「この id のタスクを消す」
=「この id 以外のタスクだけを残す」
JavaScript の配列では、filter がぴったりです。
function deleteTask(id) {
state.tasks = state.tasks.filter((t) => t.id !== id);
render();
}
JavaScriptここでの重要ポイントは、
filterは「条件を満たす要素だけを残した新しい配列」を返す- それをそのまま
state.tasksに代入している
というところです。
「配列を直接いじる(splice など)」よりも、
“新しい配列を作って入れ替える”ほうがバグりにくいという感覚も、
少しずつ意識しておくと良いです。
DOM と state をつなぐ:完了ボタンと削除ボタンに id を渡す
render の中で「ボタンに id を持たせる」
1日目の render を、少しだけ拡張します。
function render() {
tasksContainerEl.innerHTML = "";
const visibleTasks = getVisibleTasks();
visibleTasks.forEach((task) => {
const rowEl = document.createElement("div");
rowEl.className = "task-item";
const titleEl = document.createElement("div");
titleEl.className = "task-title";
if (task.done) {
titleEl.classList.add("done");
}
titleEl.textContent = task.title;
const actionsEl = document.createElement("div");
actionsEl.className = "task-actions";
const toggleButton = document.createElement("button");
toggleButton.textContent = task.done ? "未完了に戻す" : "完了";
toggleButton.addEventListener("click", () => {
toggleTaskDone(task.id);
});
const deleteButton = document.createElement("button");
deleteButton.textContent = "削除";
deleteButton.addEventListener("click", () => {
deleteTask(task.id);
});
actionsEl.appendChild(toggleButton);
actionsEl.appendChild(deleteButton);
rowEl.appendChild(titleEl);
rowEl.appendChild(actionsEl);
tasksContainerEl.appendChild(rowEl);
});
}
JavaScriptここで深掘りしたいのは、
- render の中でイベントリスナーを付けている
- そのときに「どのタスクか」を示すために task.id をクロージャで閉じ込めている
という点です。
「ボタンが押されたときに、どのタスクを操作するか」を
id でつなぐのが、設計としてとてもきれいです。
フィルタと完了状態の連動を体験する
filter の値で「見せるタスク」を変える
1日目で作った getVisibleTasks をもう一度見ます。
function getVisibleTasks() {
if (state.filter === "all") {
return state.tasks;
}
if (state.filter === "active") {
return state.tasks.filter((task) => !task.done);
}
if (state.filter === "completed") {
return state.tasks.filter((task) => task.done);
}
return state.tasks;
}
JavaScriptここでのポイントは、
- state.tasks は「全タスク」
- filter に応じて「見せるタスク」を絞り込んでいるだけ
ということです。
完了ボタンを押して task.done が true になれば、
「未完了フィルタ」では見えなくなり、
「完了フィルタ」では見えるようになります。
つまり、
- データ(state.tasks)は 1 つ
- 見せ方(filter)は複数
という構造になっています。
タスク追加の UX を少しだけ良くする
Enter キーでも追加できるようにする
ボタンだけでなく、Enter キーでも追加できると気持ちいいです。
taskInputEl.addEventListener("keydown", (event) => {
if (event.key === "Enter") {
addTask(taskInputEl.value);
taskInputEl.value = "";
}
});
JavaScript空文字は追加しない
1日目で書いた addTask をもう一度確認します。
let nextId = 1;
function addTask(title) {
const trimmed = title.trim();
if (!trimmed) {
return;
}
const newTask = {
id: nextId++,
title: trimmed,
done: false,
};
state.tasks.push(newTask);
render();
}
JavaScriptここでのポイントは、
trim()で前後の空白を削除してからチェックしている- 空文字なら何もしない(state を変えない)
という「入力の最低限のバリデーション」です。
状態管理の“感覚”をつかむためのミニ実験
実験 1:コンソールからタスクを追加してみる
ブラウザのコンソールを開いて、こう打ってみてください。
state.tasks.push({ id: 999, title: "コンソールから追加", done: false });
render();
JavaScript画面に「コンソールから追加」が出てきたら、
「画面は state の結果である」という感覚が、少しリアルになります。
実験 2:filter を直接変えてみる
同じくコンソールで、
state.filter = "completed";
render();
JavaScriptとすると、「完了タスクだけ」が表示されます。
この感覚が大事です。
- 画面を直接いじるのではなく
- state を変えて、render で画面を“再計算”する
これが「状態管理(state)+再描画ロジック」の基本です。
2日目のまとめと、明日へのつなぎ
今日あなたがやったことは、ToDo アプリの「動く中身」のコアです。
- タスクを配列+オブジェクトで管理し、id で識別した
- 完了切り替え(done true/false)を id ベースで実装した
- 削除を filter で「残すものだけ残す」形で実装した
- filter と done 状態の連動を体験した
- state を変えたら render で画面を作り直す、という流れを体に入れた
明日(3日目)はここから、
- フィルタボタンの「選択中スタイル」を state と連動させる
- タスク数(全件/未完了)のカウンターを追加する
- 設計を少し整理して、関数を分割する
といった「設計の磨き込み」に入っていきます。
もし余力があれば、2日目のうちに
- タスクを 5 件くらい追加して、完了/削除/フィルタをいじり倒す
toggleTaskDoneやdeleteTaskの中にconsole.log(state.tasks)を入れて、
配列がどう変わっているか眺めてみる
など、「中身の配列」と「画面の動き」を結びつけてみてください。
そこがつながると、一気に“設計が見える人”になっていきます。

