JavaScript | 2週間で身につく、アプリを作りながら学ぶJavaScriptの基本 - 8日目

JavaScript JavaScript
スポンサーリンク

8日目のゴールとテーマ

8日目のテーマは「タスク管理アプリ(ToDoリスト)の“ちゃんと動く形”を作る」です。
これまでの名簿アプリで学んだことを、別テーマのアプリに“移植”していきます。

タスクを追加する。
タスクを「完了」に切り替える。
タスクを削除する。

この三つを、ブラウザ上でちゃんと動く形にして、「あ、自分もう普通にアプリ作ってるな」という感覚をつかむのが今日のゴールです。


今日作るタスク管理アプリのイメージ

どんな動きをするアプリにするか

まずは、仕様を言葉で決めます。

画面上に「タスク名を入力するテキストボックス」と「追加ボタン」がある。
下に「タスク一覧」が表示される。
各タスクには「完了ボタン」と「削除ボタン」が付いている。
完了したタスクは、表示上「[完了]」と分かるようにする。

これを、名簿アプリと同じように「配列+オブジェクト+render関数」で組み立てていきます。


HTMLの土台を用意する

入力欄・追加ボタン・一覧表示エリア

index.html を次のように用意します。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>8日目:タスク管理アプリ</title>
</head>
<body>
  <h1>タスク管理アプリ</h1>

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

  <h2>タスク一覧</h2>
  <div id="task-list-area">
    まだタスクがありません。
  </div>

  <script src="main.js"></script>
</body>
</html>
HTML

ここで重要なのは、三つの要素に id を付けていることです。
タスク名を入力する input に id="task-input"
追加ボタンに id="add-task-button"
タスク一覧を表示するエリアに id="task-list-area"

JavaScript は、これらをつかまえて操作していきます。


JavaScript側の「状態」として tasks 配列を持つ

1件分のタスクをオブジェクトで表す

main.js を作り、まずは次のように書きます。

let tasks = [];

let taskInputElement = document.getElementById("task-input");
let addTaskButtonElement = document.getElementById("add-task-button");
let taskListAreaElement = document.getElementById("task-list-area");
JavaScript

ここでのポイントは、「tasks 配列がアプリの“心臓”になる」ということです。
1件分のタスクは、次のようなオブジェクトで表します。

// 例
let task = {
  title: "牛乳を買う",
  done: false
};
JavaScript

title はタスクの名前。
done は「完了しているかどうか」を表す真偽値です。
false なら未完了、true なら完了。

この「1件分のオブジェクト」を tasks 配列にどんどん入れていきます。


タスク一覧を描画する renderTasks 関数を作る

配列の中身から HTML を組み立てる

名簿アプリと同じように、「配列から画面を描く」関数を作ります。

function renderTasks() {
  if (tasks.length === 0) {
    taskListAreaElement.textContent = "まだタスクがありません。";
    return;
  }

  let html = "";

  for (let i = 0; i < tasks.length; i = i + 1) {
    let task = tasks[i];

    let statusText = task.done ? "[完了]" : "[未完了]";

    html = html +
      '<div>' +
        statusText + " " + task.title +
        ' <button data-index="' + i + '" data-action="toggle">完了切替</button>' +
        ' <button data-index="' + i + '" data-action="delete">削除</button>' +
      '</div>';
  }

  taskListAreaElement.innerHTML = html;
}
JavaScript

ここでの重要ポイントを丁寧に見ていきます。

まず、タスクが0件のときは特別扱いして、「まだタスクがありません。」と表示して return しています。
これにより、「空のときの表示」と「あるときの表示」をきれいに分けられます。

次に、html という文字列を用意して、for ループの中でどんどん足していっています。
1件のタスクにつき、<div>...</div> を1つ作っています。

statusText は、done が true なら “[完了]”、false なら “[未完了]” にしています。
三項演算子 条件 ? A : B を使うと、こういう「条件によって文字列を変える」がコンパクトに書けます。

ボタンの部分に注目してください。
data-index="data-action=" という属性を付けています。
これは「このボタンは tasks 配列の何番目に対応しているか」「このボタンは何をするボタンか」を、HTML側に埋め込んでいるイメージです。

最後に、taskListAreaElement.innerHTML = html; で、組み立てた HTML を一気に差し込んでいます。
textContent ではなく innerHTML を使っているのは、「HTMLタグを含んだ文字列」を入れたいからです。


タスクを追加する処理を書く

入力値からオブジェクトを作り、配列に push して render

次に、「追加」ボタンのクリックイベントを書きます。

addTaskButtonElement.addEventListener("click", function () {
  let title = taskInputElement.value;

  if (title === "") {
    alert("タスクを入力してください。");
    return;
  }

  let task = {
    title: title,
    done: false
  };

  tasks.push(task);

  taskInputElement.value = "";

  renderTasks();
});
JavaScript

ここでの重要ポイントは三つです。

一つ目、入力欄から value を取り出していること。
空文字だったら意味がないので、alert して return しています。

二つ目、task オブジェクトを作るときに、done を必ず false にしていること。
追加された直後のタスクは「未完了」なので、初期値は false です。

三つ目、tasks.push(task) で配列に追加したあと、renderTasks() を呼んでいること。
「状態(tasks)が変わったら、必ず render する」という筋を通しています。


初期表示で renderTasks を呼んでおく

ページを開いた瞬間の状態も関数で描く

main.js の最後に、次の1行を足します。

renderTasks();
JavaScript

これで、ページを開いた瞬間にも「まだタスクがありません。」と表示されます。
「最初も render、変更後も render」という形にしておくと、頭の中がとても整理されます。


完了切り替えと削除をどう実装するか

各ボタンに直接イベントを付けるか、親でまとめて受けるか

タスクごとに「完了切替」「削除」ボタンがあります。
これらのクリックをどう拾うか、方法は二つあります。

一つは、「renderTasks の中でボタンを作ったあと、1個1個に addEventListener を付ける」方法。
もう一つは、「親要素(task-list-area)に1つだけイベントを付けて、クリックされた要素を調べる」方法です。

今日は、後者の「親でまとめて受ける」方法(イベント委譲)を使います。
理由は、タスクが増えたり減ったりするたびにイベントを付け直さなくてよくて、コードがスッキリするからです。


親要素でクリックをまとめて受ける(イベント委譲)

data 属性を手がかりに「何番目の何ボタンか」を判定する

taskListAreaElement にクリックイベントを付けます。

taskListAreaElement.addEventListener("click", function (event) {
  let target = event.target;

  if (target.tagName !== "BUTTON") {
    return;
  }

  let indexText = target.getAttribute("data-index");
  let action = target.getAttribute("data-action");

  if (indexText === null || action === null) {
    return;
  }

  let index = Number(indexText);

  if (Number.isNaN(index)) {
    return;
  }

  if (action === "toggle") {
    tasks[index].done = !tasks[index].done;
    renderTasks();
  } else if (action === "delete") {
    tasks.splice(index, 1);
    renderTasks();
  }
});
JavaScript

ここでの重要ポイントをじっくり見ていきます。

event.target は、「実際にクリックされた要素」です。
ボタン以外がクリックされたときは何もしないので、target.tagName !== "BUTTON" なら return しています。

次に、getAttribute("data-index")getAttribute("data-action") で、
renderTasks の中で埋め込んだ data 属性を取り出しています。

indexText は文字列なので、Number に変換して index にしています。
Number.isNaN で念のためチェックしています。

action が “toggle” のときは、「完了切替ボタン」です。
tasks[index].done = !tasks[index].done; で、true と false を反転させています。
そのあと renderTasks() で画面を更新します。

action が “delete” のときは、「削除ボタン」です。
tasks.splice(index, 1); で、tasks 配列から index 番目の要素を1件削除しています。
そのあと renderTasks() で画面を更新します。

ここでのキモは、「画面を直接いじって消す」のではなく、
「配列(状態)を変えてから render し直す」という流れを守っていることです。


完了タスクの見た目を少し変える

done に応じて表示を変える

今は “[完了]” と “[未完了]” の文字だけで区別していますが、
もう少し分かりやすくしてみます。

例えば、「完了タスクはタイトルの前に ‘✔’ を付ける」「未完了は ‘・’ にする」などです。

renderTasks の中の statusText を少し変えます。

let mark = task.done ? "✔" : "・";
let statusText = task.done ? "[完了]" : "[未完了]";
JavaScript

そして、表示部分をこうします。

html = html +
  '<div>' +
    mark + " " + statusText + " " + task.title +
    ' <button data-index="' + i + '" data-action="toggle">完了切替</button>' +
    ' <button data-index="' + i + '" data-action="delete">削除</button>' +
  '</div>';
JavaScript

これで、完了タスクは「✔ [完了] タスク名」、未完了は「・ [未完了] タスク名」のように表示されます。
見た目の工夫は CSS でやるのが本筋ですが、8日目の段階では「文字だけで工夫する」でも十分です。


8日目で一番大事な感覚

「同じパターンを別のアプリに移植できた」

今日あなたがやったことを、あえて抽象的に言うとこうです。

1件分のデータをオブジェクトで表す。
その配列を「アプリの状態」として持つ。
状態が変わったら、render 関数で画面を描き直す。
ボタンのクリックなどのイベントで、状態を変える。

これは、名簿アプリでもタスク管理アプリでも、まったく同じパターンです。
つまり、「パターンを理解して、別テーマに適用できた」ということです。

ここまで来ると、「アプリを作る」という行為が、
だんだん“謎の魔法”ではなく、“再現可能な手順”に見えてきたはずです。


8日目のまとめ

今日のキーポイントを短く整理すると、こうなります。

タスク管理アプリの仕様を言葉で決めてから、HTMLとJavaScriptに落とし込んだ。
1件分のタスクを { title, done } のオブジェクトで表し、tasks 配列に入れた。
renderTasks 関数で、tasks から HTML を組み立てて innerHTML に流し込む形を作った。
追加ボタンで、入力値からタスクオブジェクトを作り、配列に push → render という流れを書いた。
イベント委譲(親要素でクリックをまとめて受ける)と data 属性を使って、「完了切替」と「削除」を実装した。

次の9日目では、このタスク管理アプリをベースに、
「未完了だけ表示」「完了タスクを一括削除」「タスク数のカウント表示」など、
もう一段階“実用アプリ寄り”の機能を足していきます。

もし余裕があれば、
今日のアプリに「作成日時を持たせる」「古い順・新しい順に並び替える」など、
自分なりのアイデアを1つだけでも足してみてください。
その「こうなったら便利だな」を形にする感覚が、エンジニアとしての感性を一番育ててくれます。

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