3日目のゴールと今日のテーマ
3日目は「設計を一段きれいにする日」です。
1〜2日目で、ToDoアプリはすでに「動くもの」になりましたが、
今日はそこから一歩進めて、
- 状態管理(state)を少し整理する
- フィルタの選択状態を state と連動させる
- タスク数(全件/未完了)のカウンターを追加する
- render の責務をはっきりさせて“読みやすいコード”に近づける
ここを丁寧にやっていきます。
「動くコード」から「読めるコード」へ、設計のレベルを上げるイメージです。
state を少しだけ“設計っぽく”整理する
state を「意味ごと」に分けて考える
今の state はこうでした。
const state = {
tasks: [],
filter: "all", // "all" | "active" | "completed"
};
JavaScriptこれは悪くないですが、3日目では「何のための state か」を
もう少し意識して整理してみます。
例えば、こう分けて考えられます。
- データそのもの(tasks)
- 表示の状態(filter)
この意識を持つだけで、
「これはデータ」「これは UI の状態」という区別がつきやすくなります。
さらに、今日からは「フィルタボタンの選択状態」も UI の一部として扱います。
ただし、filter がすでに "all" | "active" | "completed" を持っているので、
それをそのまま「選択状態のソース」として使います。
つまり、
「どのフィルタが選ばれているか」は、
state.filter を見ればわかるようにしておきます。
フィルタボタンの「選択中スタイル」を state と連動させる
HTML にクラス用のスタイルを追加する
まずは CSS に「選択中のフィルタボタン」の見た目を足します。
<style>
.filter-row button.selected {
background-color: #007bff;
color: #fff;
}
</style>
これで、class="selected" が付いたボタンは
「選ばれている感」が出るようになります。
render の中で「どのボタンが選ばれているか」を反映する
フィルタボタンをまとめて取得しておきます。
const filterButtons = document.querySelectorAll(".filter-row button");
JavaScriptrender の中に、フィルタボタンの更新処理を追加します。
function render() {
tasksContainerEl.innerHTML = "";
const visibleTasks = getVisibleTasks();
visibleTasks.forEach((task) => {
// ここは前回までと同じ(タスク行の生成)
});
updateFilterButtons();
}
JavaScriptupdateFilterButtons を定義します。
function updateFilterButtons() {
filterButtons.forEach((button) => {
const filter = button.dataset.filter;
if (filter === state.filter) {
button.classList.add("selected");
} else {
button.classList.remove("selected");
}
});
}
JavaScriptここでの重要ポイントは、
- 「どのボタンが選ばれているか」は state.filter が真実
- ボタン側は「state に合わせて見た目を変えるだけ」
という一方向の関係になっていることです。
ボタンが「自分で勝手に選択状態を持つ」のではなく、
state を見て従うだけという設計がきれいです。
タスク数のカウンターを追加して「状態を数字で見る」
HTML にカウンター表示用の場所を作る
index.html に、タスク数を表示するエリアを追加します。
<div class="summary-row">
<span id="task-count"></span>
</div>
CSS も軽く整えます。
<style>
.summary-row {
margin-top: 8px;
font-size: 12px;
color: #555;
}
</style>
state から「全件数」と「未完了数」を計算する
app.js で要素を取得します。
const taskCountEl = document.getElementById("task-count");
JavaScriptrender の最後に、カウンター更新処理を追加します。
function render() {
tasksContainerEl.innerHTML = "";
const visibleTasks = getVisibleTasks();
visibleTasks.forEach((task) => {
// タスク行の生成
});
updateFilterButtons();
updateTaskCount();
}
JavaScriptupdateTaskCount を定義します。
function updateTaskCount() {
const total = state.tasks.length;
const activeCount = state.tasks.filter((t) => !t.done).length;
taskCountEl.textContent = `全 ${total} 件(未完了 ${activeCount} 件)`;
}
JavaScriptここでの深掘りポイントは、
- カウンターは「state から計算される値」であり、
別に state に持つ必要はない(冗長になる)こと - 「表示用の値」は、
「元データ(tasks)から計算する」という発想を持つこと
です。
これができると、「どこに何を持つべきか」の感覚が育ちます。
render の責務をはっきりさせて“読みやすくする”
今の render を分解してみる
2日目までの render は、
「タスク一覧の描画」と「フィルタボタンの更新」と「カウンター更新」が
ごちゃっと一緒に書かれがちです。
3日目では、責務ごとに関数を分けてみます。
function render() {
renderTaskList();
updateFilterButtons();
updateTaskCount();
}
JavaScriptrenderTaskList を定義します。
function renderTaskList() {
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 は「画面全体の更新フロー」をまとめるだけ
- 実際の描画は
renderTaskListなどの小さい関数に任せる
という構造にすることです。
こうすると、コードを読むときに
「画面全体の流れ」
→「タスク一覧の描画」
→「フィルタボタンの更新」
→「カウンターの更新」
という順番で理解しやすくなります。
状態管理の“設計視点”をもう一段深掘りする
「どこからでも render を呼べる」設計の意味
今の設計では、
- タスク追加
- 完了切り替え
- 削除
- フィルタ変更
どの操作でも、最後に render() を呼んでいます。
これは一見「雑」に見えるかもしれませんが、
実はとても強い設計です。
理由はシンプルで、
- 画面の真実は常に state にある
- render は「今の state をそのまま画面に反映する」だけ
だからです。
「ここだけ DOM を直接いじろう」としないこと。
これが中級以上の設計ではとても大事です。
「state を変える関数」と「画面を描く関数」を分ける
今の構造はこうなっています。
- addTask:state.tasks を増やす → render
- toggleTaskDone:state.tasks の中身を変える → render
- deleteTask:state.tasks を差し替える → render
- filter 変更:state.filter を変える → render
つまり、
- 「状態を変える関数」
- 「状態から画面を作る関数(render)」
が、きれいに分かれています。
この分離ができていると、
後で「ローカル保存」「サーバー連携」などを足すときにも、
設計が崩れにくくなります。
3日目のまとめと、明日へのつなぎ
今日あなたがやったのは、「動く ToDo」を「設計された ToDo」に近づける作業です。
- state を「データ」と「表示状態」に分けて意識した
- filter を state の真実として扱い、ボタンはそれに従わせた
- タスク数(全件/未完了)を state から計算して表示した
- render の責務を分割し、読みやすい構造にした
- 「状態を変える関数」と「画面を描く関数」を分ける感覚をつかんだ
明日(4日目)はここから、
- タスクの編集機能(タイトル変更)を設計してみる
- 「id でタスクを特定する」設計をさらに活かす
- 小さな UI 改善(空のときのメッセージ表示など)
といった方向に進めていきます。
もし余力があれば、3日目のうちに
- タスクをいくつか追加して、フィルタを切り替えながらカウンターの数字を眺める
updateTaskCountの中でconsole.logを入れて、totalとactiveCountがどう変わるか見てみる
など、「state の変化」と「画面の変化」を結びつけて感じてみてください。
そこがつながると、設計のセンスが一気に伸びていきます。

