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

JavaScript JavaScript
スポンサーリンク

Day25 後半のゴール

前半で「タスクを入力して、画面に追加する」ところまではできました。
後半ではそこから一歩進めて、

タスクを配列で管理する
「画面」と「データ」を分けて考える
後で削除や完了機能を足しやすい形にする

ここを丁寧に作っていきます。
いきなり高機能にはしないけれど、「設計として強い TODO」に育てていきます。


なぜ配列でタスクを管理するのか

画面だけをいじると、すぐ限界が来る

前半のコードは、ボタンを押すたびに p 要素を作って、そのまま画面に追加していました。
これは「とりあえず動かす」には十分ですが、次のようなことをやろうとすると困ります。

特定のタスクだけ削除したい
完了したタスクだけ見た目を変えたい
後で localStorage に保存したい

なぜ困るかというと、「タスクの本当の姿」が DOM の中にしかないからです。
どのタスクが何番目なのか、どんな状態なのか、JavaScript 側で把握しづらい。

そこで、「タスクの本体は配列で持つ」「画面はその写し」という形に変えます。


タスク配列を導入する

タスクの「リスト」をコードの中に持つ

まず、タスクを入れておく配列を用意します。

const taskInputElement = document.getElementById("taskInput");
const addButtonElement = document.getElementById("addButton");
const taskListElement = document.getElementById("taskList");

let tasks = [];
JavaScript

ここで tasks は、「タスクの一覧」を表す配列です。
最初は空ですが、タスクを追加するたびに中身が増えていきます。

この時点で、目線を少し変えます。

画面にある p 要素たちが「真実」ではない
tasks 配列が「真実」で、画面はそのコピー

という考え方に切り替えます。


配列から画面を描画する関数を作る

renderTasks という「出力担当」を作る

配列を導入したら、次にやるのは「配列の中身を画面に反映する」関数づくりです。

function renderTasks() {
  taskListElement.textContent = "";

  tasks.forEach((text) => {
    const taskElement = document.createElement("p");
    taskElement.textContent = text;
    taskListElement.appendChild(taskElement);
  });
}
JavaScript

ここでやっていることはシンプルです。

一度、一覧を空にする
tasks の中身を順番に取り出して p 要素を作る
それを taskListElement に追加する

これで、「tasks 配列さえ正しければ、画面はいつでも再構築できる」状態になります。
この構造が、後で効いてきます。


追加処理を「配列+描画」に書き換える

直接 DOM をいじるのではなく、配列を更新する

前半では、クリック時に直接 p 要素を作っていました。
後半では、「配列を更新 → 画面を再描画」という流れに変えます。

addButtonElement.addEventListener("click", () => {
  const text = taskInputElement.value.trim();

  if (text === "") {
    return;
  }

  tasks.push(text);

  renderTasks();

  taskInputElement.value = "";
});
JavaScript

ここでの流れはこうです。

入力欄からテキストを取得する
空なら何もしない
tasks 配列に追加する
renderTasks で画面を更新する

この形にしておくと、例えば「削除」機能を付けたいときも、

tasks から該当の要素を消す
renderTasks を呼ぶ

だけで済みます。
画面を直接いじり回さなくてよくなるのが、配列管理の強さです。


関数分割で役割をさらに整理する

addTask と renderTasks に分ける

今のコードでも十分動きますが、もう一歩だけ整理してみます。
「タスクを追加する」という処理を関数に切り出します。

function addTask(text) {
  tasks.push(text);
}
JavaScript

イベントハンドラはこう書き換えられます。

addButtonElement.addEventListener("click", () => {
  const text = taskInputElement.value.trim();

  if (text === "") {
    return;
  }

  addTask(text);
  renderTasks();
  taskInputElement.value = "";
});
JavaScript

これで、役割がはっきりします。

addTask
「データ(tasks 配列)を更新する」担当

renderTasks
「画面(DOM)を更新する」担当

イベントハンドラ
「入力を受け取り、処理の流れを組み立てる」担当

この分け方は、アプリが大きくなっても通用する設計の基本です。


例題:削除機能を見据えた設計のイメージ

今は実装しないけれど、頭の中でシミュレーションしてみる

今は「タスク追加」だけですが、
この先ほぼ確実に「削除したい」「完了にしたい」という欲が出てきます。

そのときのことを、少しだけイメージしてみましょう。

削除したいタスクを特定するには、
tasks の「何番目か」が分かっていると便利です。
renderTasks の中で index を使えば、それも簡単に扱えます。

function renderTasks() {
  taskListElement.textContent = "";

  tasks.forEach((text, index) => {
    const taskElement = document.createElement("p");
    taskElement.textContent = text;

    taskListElement.appendChild(taskElement);
  });
}
JavaScript

もし削除ボタンを付けるなら、index を使って

tasks.splice(index, 1);
renderTasks();

という流れにできます。

ここで大事なのは、「配列が真実」という設計にしておくと、
こういう拡張が自然にできる、という感覚です。


デバッグ視点で配列と描画を確認する

「配列の中身」と「画面の状態」を意識して見る

もし、

タスクを追加しても表示されない
なぜか同じタスクが増え続ける

といったことが起きたら、次のように console.log を入れてみてください。

function addTask(text) {
  tasks.push(text);
  console.log("現在の tasks:", tasks);
}

function renderTasks() {
  console.log("renderTasks を実行します");
  taskListElement.textContent = "";

  tasks.forEach((text, index) => {
    console.log("描画するタスク:", index, text);
    const taskElement = document.createElement("p");
    taskElement.textContent = text;
    taskListElement.appendChild(taskElement);
  });
}
JavaScript

これで、

クリックのたびに tasks がどう変わっているか
renderTasks がちゃんと呼ばれているか
forEach でどんな値が回ってきているか

を一つずつ確認できます。

「配列の状態」と「画面の状態」を分けて意識できるようになると、
バグの原因も見つけやすくなります。


Day25 後半の完成コード

配列管理まで含めた TODO①

ここまでの内容をまとめると、こうなります。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>TODOアプリ</title>
  </head>
  <body>
    <h1>TODOアプリ</h1>

    <h2>タスク追加</h2>
    <input id="taskInput" type="text" placeholder="やることを入力">
    <button id="addButton">追加</button>

    <h2>タスク一覧</h2>
    <div id="taskList"></div>

    <script>
      const taskInputElement = document.getElementById("taskInput");
      const addButtonElement = document.getElementById("addButton");
      const taskListElement = document.getElementById("taskList");

      let tasks = [];

      function addTask(text) {
        tasks.push(text);
      }

      function renderTasks() {
        taskListElement.textContent = "";

        tasks.forEach((text) => {
          const taskElement = document.createElement("p");
          taskElement.textContent = text;
          taskListElement.appendChild(taskElement);
        });
      }

      addButtonElement.addEventListener("click", () => {
        const text = taskInputElement.value.trim();

        if (text === "") {
          return;
        }

        addTask(text);
        renderTasks();
        taskInputElement.value = "";
      });
    </script>
  </body>
</html>

見た目は前半とあまり変わりません。
でも中身は、

タスクを配列で管理している
画面は配列から再描画している
追加処理と描画処理が関数で分かれている

という、かなり「アプリらしい」構造になっています。


Day25 後半のまとめ

後半で押さえたポイントは、次のようなものです。

タスクを配列(tasks)で管理するようにした
配列から画面を描画する renderTasks を作った
タスク追加処理を addTask に分けて、役割を整理した
「配列が真実、画面はその写し」という設計に切り替えた
デバッグするときは「配列の中身」と「描画の流れ」を分けて見る

この形まで来ると、
次のステップである「削除」「完了」「保存(localStorage)」が、
無理なく積み上げられるようになります。

ここから先は、もう「自分の TODO をどう育てたいか」という世界です。
その土台として、今日の設計はかなりいい線いってます。

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