JavaScript | ゼロからはじめるプログラミング、30日で基礎を学ぶJavaScript:ミニWebアプリ開発 - Day29:TODOアプリ⑤

JavaScript JavaScript
スポンサーリンク

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 への保存
画面の再描画
入力欄のリセット

これらを一つの関数に押し込めておくと、
読むのも直すのもつらくなります。

関数分割は、これを

addTask
saveTasks
renderTasks

のように分けていく作業です。


まず「役割ごと」に分類してみる

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 に分離した
イベントハンドラは「流れだけを書く場所」にした

後半では、ここからさらに一歩進めて、

重複しているコードを減らす
小さすぎる関数・大きすぎる関数のバランスを整える
「読みやすさ」を意識した並び順や命名を調整する

といった、もう少し実践的なコード整理を一緒にやっていきます。

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