JavaScript | 1 日 120 分 × 7 日アプリ学習:ToDoアプリ(設計力強化編)

JavaScript
スポンサーリンク

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");
JavaScript

render の中に、フィルタボタンの更新処理を追加します。

function render() {
  tasksContainerEl.innerHTML = "";

  const visibleTasks = getVisibleTasks();

  visibleTasks.forEach((task) => {
    // ここは前回までと同じ(タスク行の生成)
  });

  updateFilterButtons();
}
JavaScript

updateFilterButtons を定義します。

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");
JavaScript

render の最後に、カウンター更新処理を追加します。

function render() {
  tasksContainerEl.innerHTML = "";

  const visibleTasks = getVisibleTasks();

  visibleTasks.forEach((task) => {
    // タスク行の生成
  });

  updateFilterButtons();
  updateTaskCount();
}
JavaScript

updateTaskCount を定義します。

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();
}
JavaScript

renderTaskList を定義します。

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 を入れて、
    totalactiveCount がどう変わるか見てみる

など、「state の変化」と「画面の変化」を結びつけて感じてみてください。
そこがつながると、設計のセンスが一気に伸びていきます。

タイトルとURLをコピーしました