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);
});
}
JavaScript1日目では、まだ完了・削除のイベントは付けません。
「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 を切り替えて、表示がどう変わるか確認する
など、「設計したものがどう動くか」を自分の目で確かめてみてください。
ここから先は、この土台の上にどんどん機能を積み上げていきます。

