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

JavaScript
スポンサーリンク

1日目のゴールと今日のテーマ

1日目のテーマは「ToDoアプリの“設計の土台”をつかむこと」です。
いきなりコードを書くのではなく、

  • 状態管理(state)をどう持つか
  • データ構造(配列+オブジェクト)をどう設計するか
  • 再描画(render)の流れをどう作るか

を、頭の中でハッキリさせるのがゴールです。

今日は次のところまで行きます。

  • 最小限の HTML で ToDo 画面の骨組みを作る
  • 「タスクを配列+オブジェクトで持つ」感覚をつかむ
  • state と render の“往復”を体験する(まだ機能は最小)

完了・削除・フィルタは、今日は「設計のイメージ」まで。
実装は 2 日目以降にじっくりやります。


アプリの全体像を言葉で設計する

この ToDo アプリでやりたいこと

最終的に目指す ToDo アプリは、こんな機能を持ちます。

  • タスクを入力して「追加」できる
  • タスクを「完了」にできる(チェック or ボタン)
  • タスクを「削除」できる
  • 表示を「全件 / 完了 / 未完了」で切り替えられる

そして、データはこう持ちます。

  • 配列(tasks)の中に
  • 1 件ずつ「オブジェクト」でタスクを表現する
const tasks = [
  { id: 1, title: "牛乳を買う", done: false },
  { id: 2, title: "メール返信", done: true },
];
JavaScript

ここで重要なのは、

  • 「画面に見えているもの」は全部この配列から作られる
  • 画面を直接いじるのではなく、「配列 → 画面」の一方向にする

という設計の考え方です。


HTMLで「最小の ToDo 画面」を作る

index.html の骨組み

まずは、次のような HTML を用意します(ul/li は使わず、div で構成します)。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8" />
  <title>ToDoアプリ(設計力強化編)</title>
  <style>
    body { font-family: sans-serif; padding: 20px; background: #f8f9fa; }
    .app-title { font-size: 20px; margin-bottom: 16px; }
    .task-input-row { margin-bottom: 16px; }
    .task-input-row input { padding: 4px 8px; width: 240px; }
    .task-input-row button { padding: 4px 10px; margin-left: 8px; }
    .filter-row button { margin-right: 6px; padding: 4px 10px; }
    .tasks-container { margin-top: 16px; }
    .task-item { display: flex; align-items: center; margin-bottom: 8px; }
    .task-title { flex: 1; }
    .task-title.done { text-decoration: line-through; color: #888; }
    .task-actions button { margin-left: 6px; }
  </style>
</head>
<body>
  <div class="app">
    <div class="app-title">ToDoアプリ(設計力強化編)</div>

    <div class="task-input-row">
      <input id="task-input" type="text" placeholder="タスクを入力..." />
      <button id="add-button">追加</button>
    </div>

    <div class="filter-row">
      <button data-filter="all">全件</button>
      <button data-filter="active">未完了</button>
      <button data-filter="completed">完了</button>
    </div>

    <div id="tasks-container" class="tasks-container"></div>
  </div>

  <script src="app.js"></script>
</body>
</html>

ここで押さえておきたいのは、

  • タスク一覧は <div id="tasks-container"> の中に「後から JavaScript で差し込む」
  • フィルタボタンには data-filter 属性で「役割」を持たせている
  • 1 タスクは .task-item という 1 つの「行」として表現する

という「画面の構造」です。


state を設計する:何を覚えておく必要があるか

state の中身を決める

ToDo アプリで「覚えておくべき情報」は何か?
コードを書く前に、言葉で整理します。

  • すべてのタスク(配列)
  • 今どのフィルタが選ばれているか(全件 / 未完了 / 完了)

これを JavaScript のオブジェクトとして持ちます。

const state = {
  tasks: [],
  filter: "all", // "all" | "active" | "completed"
};
JavaScript

ここでの重要ポイントは、

  • 画面に必要な情報は、必ず state に集約する
  • DOM(画面)は state の“結果”として描画するだけ

という「一方向データフロー」の考え方です。


タスクのデータ構造を決める:配列+オブジェクト

1 タスクをどう表現するか

1 件のタスクは、オブジェクトで表現します。

const task = {
  id: 1,
  title: "牛乳を買う",
  done: false,
};
JavaScript
  • id:一意な番号(後で完了・削除のときに使う)
  • title:タスクの名前(文字列)
  • done:完了しているかどうか(true / false)

これを配列に入れていきます。

state.tasks = [
  { id: 1, title: "牛乳を買う", done: false },
  { id: 2, title: "メール返信", done: true },
];
JavaScript

ここで深掘りしたいのは、

  • 「配列の何番目か」ではなく「id」でタスクを識別する設計
  • オブジェクトに「意味のあるプロパティ名」を付けること

です。
これが後の「完了」「削除」「フィルタ」で効いてきます。


再描画ロジックの基本:render 関数を作る

render の役割をはっきりさせる

render 関数は、「state を読んで画面を作り直す」役割だけを持たせます。

const tasksContainerEl = document.getElementById("tasks-container");

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 ? "未完了に戻す" : "完了";
    // ここに後でイベントを付ける

    const deleteButton = document.createElement("button");
    deleteButton.textContent = "削除";
    // ここに後でイベントを付ける

    actionsEl.appendChild(toggleButton);
    actionsEl.appendChild(deleteButton);

    rowEl.appendChild(titleEl);
    rowEl.appendChild(actionsEl);

    tasksContainerEl.appendChild(rowEl);
  });
}
JavaScript

1日目では、まだ完了・削除のイベントは付けません。
「state から DOM を組み立てる流れ」を体で覚えるのが目的です。

フィルタに応じて表示するタスクを決める

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

ここでの重要ポイントは、

  • 「絞り込み」は配列の filter で行う
  • state.tasks 自体は変えず、「見せ方」だけ変える

という設計です。
データ本体を壊さずに、表示だけ切り替えるのがきれいなやり方です。


タスク追加の流れを設計する(まだ最小)

入力 → state 更新 → render の一方向

app.js に、入力欄とボタンを取得します。

const taskInputEl = document.getElementById("task-input");
const addButtonEl = document.getElementById("add-button");
JavaScript

タスク追加の処理を作ります。

let nextId = 1;

function addTask(title) {
  if (!title.trim()) {
    return;
  }

  const newTask = {
    id: nextId++,
    title: title.trim(),
    done: false,
  };

  state.tasks.push(newTask);
  render();
}
JavaScript

ボタンにイベントを付けます。

addButtonEl.addEventListener("click", () => {
  addTask(taskInputEl.value);
  taskInputEl.value = "";
});
JavaScript

ここでの深掘りポイントは、

  • state を更新したら、必ず render を呼ぶ
  • render は「今の state から DOM を作り直す」だけ
  • DOM を直接いじって 1 行だけ追加する、という発想を捨てる

という「状態管理の基本」です。


フィルタボタンの設計だけ先にやっておく

filter を state に反映する

フィルタボタンは、data-filter 属性を使って共通で扱います monoqlo.tokyo

const filterButtons = document.querySelectorAll(".filter-row button");

filterButtons.forEach((button) => {
  button.addEventListener("click", () => {
    const filter = button.dataset.filter;
    state.filter = filter;
    render();
  });
});
JavaScript

ここでのポイントは、

  • 「今どのフィルタか」も state の一部
  • フィルタ変更 → state 更新 → render の一方向

という流れを守ることです。


1日目のまとめと、明日へのつなぎ

1日目であなたがやったことは、かなり“設計寄り”です。

  • ToDo アプリの機能を言葉で分解した
  • state(tasks 配列+filter)を設計した
  • タスクを「配列+オブジェクト」で表現する形を決めた
  • render 関数で「state → DOM」の一方向を作った
  • filter を state に持たせ、「見せ方」を切り替える設計をした

明日(2日目)はここから、

  • 完了切り替え(done true/false)
  • 削除(id で特定して配列から除外)
  • フィルタと完了状態の連動

を実装していきます。

もし余力があれば、1日目のうちに

  • state.tasks に手動で 2〜3 件入れてから render を呼んでみる
  • filter を切り替えて、表示がどう変わるか確認する

など、「設計したものがどう動くか」を自分の目で確かめてみてください。
ここから先は、この土台の上にどんどん機能を積み上げていきます。

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