Day29 前半のゴール
Day29 のテーマは コード整理と関数分割。
ここまでで TODO アプリはかなり機能が増えましたが、そのぶんコードが長くなり、
「どこで何をしているのか」が少しずつ見えにくくなってきています。
前半ではまず、
どこが「ごちゃっとしている」のかを言葉で捉える
関数分割の考え方を理解する
小さな単位に分けて、読みやすく・直しやすくする
ここを、実際の TODO アプリのコードを題材にしながら整理していきます。
いまの TODO アプリの状態を俯瞰してみる
機能が増えると「とりあえず動くコード」になりがち
Day28 までで、TODO アプリにはこんな機能が入りました。
タスク追加
タスク削除
完了状態の切り替え
localStorage 保存・復元
機能としてはいい感じですが、
コードをよく見ると、こんな状態になっていることが多いです。
一つの関数の中で、いろんなことをやっている
同じような処理があちこちに散らばっている
「ここを直したい」と思ったときに、触る場所が多い
これを放置すると、
「動くけど触りたくないコード」になってしまいます。
Day29 では、ここを 「触りたくなるコード」に変える のがテーマです。
関数分割とは何か
「意味のあるかたまり」に名前をつけてあげること
関数分割を一言で言うと、
「やっていることのまとまりに名前をつけて、外に出す」
ということです。
例えば、こんなコードがあったとします。
addButtonElement.addEventListener("click", () => {
const text = taskInputElement.value.trim();
if (text === "") {
return;
}
const task = {
title: text,
isDone: false
};
tasks.push(task);
const json = JSON.stringify(tasks);
localStorage.setItem("tasks", json);
taskListElement.textContent = "";
tasks.forEach((task, index) => {
// タスクの要素を作って追加する処理…
});
taskInputElement.value = "";
});
JavaScriptこの中には、実はたくさんの「意味のかたまり」が混ざっています。
入力値の取得とバリデーション
タスクオブジェクトの作成と追加
localStorage への保存
画面の再描画
入力欄のリセット
これらを一つの関数に押し込めておくと、
読むのも直すのもつらくなります。
関数分割は、これを
addTasksaveTasksrenderTasks
のように分けていく作業です。
まず「役割ごと」に分類してみる
TODO アプリの処理を大きく 3 つに分ける
TODO アプリのコードは、大きく見ると次の 3 つに分けられます。
データを変える処理(ロジック)
画面を作る処理(描画)
イベントに反応する処理(入力)
これを、もう少し具体的に言い換えるとこうなります。
タスクを追加・削除・更新する関数
tasks から DOM を組み立てる関数
クリックや入力に応じて上の関数を呼ぶイベントハンドラ
Day29 前半では、
この「役割ごとに分ける」という視点を強く意識してもらいます。
データ操作の関数をはっきりさせる
addTask / deleteTask / toggleTaskDone を用意する
まずは「データを変える担当」を、きちんと関数として切り出します。
let tasks = [];
function addTask(text) {
const task = {
title: text,
isDone: false
};
tasks.push(task);
}
function deleteTask(index) {
tasks.splice(index, 1);
}
function toggleTaskDone(index) {
const task = tasks[index];
task.isDone = !task.isDone;
}
JavaScriptここでのポイントは、
これらの関数は「画面のことを一切知らない」
純粋に tasks 配列だけをいじっている
ということです。
こうしておくと、
「データのルールを変えたいときはここだけ見ればいい」
という状態になります。
画面を作る処理を一箇所にまとめる
renderTasks を「描画専用」にする
次に、「画面を作る担当」を一箇所にまとめます。
それが renderTasks です。
function renderTasks() {
taskListElement.textContent = "";
tasks.forEach((task, index) => {
const taskItemElement = document.createElement("div");
const checkboxElement = document.createElement("input");
checkboxElement.type = "checkbox";
checkboxElement.checked = task.isDone;
const taskTextElement = document.createElement("span");
taskTextElement.textContent = task.title;
if (task.isDone) {
taskTextElement.style.textDecoration = "line-through";
taskTextElement.style.color = "#888";
} else {
taskTextElement.style.textDecoration = "none";
taskTextElement.style.color = "inherit";
}
const deleteButtonElement = document.createElement("button");
deleteButtonElement.textContent = "削除";
// ここでイベントも付けるが、
// 実際にデータを変えるのは addTask / deleteTask / toggleTaskDone
checkboxElement.addEventListener("change", () => {
toggleTaskDone(index);
saveTasks();
renderTasks();
});
deleteButtonElement.addEventListener("click", () => {
deleteTask(index);
saveTasks();
renderTasks();
});
taskItemElement.appendChild(checkboxElement);
taskItemElement.appendChild(taskTextElement);
taskItemElement.appendChild(deleteButtonElement);
taskListElement.appendChild(taskItemElement);
});
}
JavaScriptここで意識してほしいのは、
renderTasks は「tasks をもとに DOM を組み立てる」のが仕事
データの変更は必ず addTask / deleteTask / toggleTaskDone を経由する
という構造です。
保存・復元も「役割の一つ」として分ける
saveTasks / loadTasks をデータの外側に置く
localStorage への保存・復元も、
一つの「役割」として関数にしておきます。
function saveTasks() {
const json = JSON.stringify(tasks);
localStorage.setItem("tasks", json);
}
function loadTasks() {
const json = localStorage.getItem("tasks");
if (json === null) {
tasks = [];
return;
}
tasks = JSON.parse(json);
}
JavaScriptこれで、
データの中身を変えるのはaddTask / deleteTask / toggleTaskDone
データを保存・復元するのはsaveTasks / loadTasks
というふうに、責任の場所がはっきりします。
イベントハンドラは「流れだけ」を書く
addButton のクリック処理を整理する
最後に、「入力に反応する担当」であるイベントハンドラを
できるだけシンプルにします。
addButtonElement.addEventListener("click", () => {
const text = taskInputElement.value.trim();
if (text === "") {
return;
}
addTask(text);
saveTasks();
renderTasks();
taskInputElement.value = "";
});
JavaScriptここでやっているのは、
入力値を取る
空なら何もしない
タスクを追加する
保存する
画面を更新する
入力欄を空にする
という「流れの説明」だけです。
実際の中身(タスクの作り方、保存の仕方、描画の仕方)は
それぞれの関数に任せています。
この状態になると、
イベントハンドラは「ストーリーを読む場所」になり、
関数は「具体的な処理が書いてある場所」になります。
Day29 前半のまとめ
前半でやったことを、言葉で整理するとこうなります。
TODO アプリの処理を「データ」「描画」「入力」に分けて考えた
データ操作を addTask / deleteTask / toggleTaskDone に切り出した
描画処理を renderTasks に集約し、「tasks から画面を作る」役に専念させた
保存・復元を saveTasks / loadTasks に分離した
イベントハンドラは「流れだけを書く場所」にした
後半では、ここからさらに一歩進めて、
重複しているコードを減らす
小さすぎる関数・大きすぎる関数のバランスを整える
「読みやすさ」を意識した並び順や命名を調整する
といった、もう少し実践的なコード整理を一緒にやっていきます。
