JavaScript | 1 日 90 分 × 7 日アプリ学習:強化版カウンターアプリ(初級編)

JavaScript
スポンサーリンク

5日目のゴールと今日のテーマ

5日目のテーマは「“強化版カウンター”として、小さなこだわり機能を入れながら設計を崩さずに育てること」です。
ここまでで、単一カウンター → 複数カウンター、イベント分離、0未満防止まできました。

今日はここに少しだけ“欲”を足します。

複数の増減ボタン(+1 / −1 に加えて、+5 / −5 など)
0未満防止をロジックとしてきれいに維持する
イベント分離を「ボタン種類」と「カウンター種類」の両方で意識する

ゴールは、

ボタンが増えてもコードがグチャグチャにならない。
「どのカウンターに」「いくつ足すか・引くか」がスッキリ書ける。
0未満防止を壊さずに機能を増やせる。

この3つを満たした状態のカウンターアプリです。


まずは昨日の「複数カウンター版」を思い出す

1カウンターをオブジェクトで表現していた

4日目では、カウンターをこう扱っていました。

const counterA = {
  count: 0,
  el: document.getElementById("counter-a"),
};

const counterB = {
  count: 0,
  el: document.getElementById("counter-b"),
};
JavaScript

そして、「1個のカウンターに対する処理」は関数にまとめていました。

function renderCounter(counter) {
  counter.el.textContent = counter.count;
}

function changeCounter(counter, delta) {
  const next = counter.count + delta;
  if (next < 0) {
    return;
  }
  counter.count = next;
  renderCounter(counter);
}

function resetCounter(counter) {
  counter.count = 0;
  renderCounter(counter);
}
JavaScript

ここでの大事なところは、「どのカウンターか」を引数で渡していることです。
これによって、A にも B にも同じロジックを使い回せていました。


5日目の一歩目:増減ボタンを増やしても壊れない形

HTMLを「ボタンの役割」が分かるようにしておく

今日は、各カウンターにこんなボタンを用意したとイメージします。

+1
−1
+5
−5
リセット

HTMLは、data-属性を使うと整理しやすいです。

<div class="counter" data-counter-id="a">
  <div id="counter-a" class="counter-value">0</div>
  <button data-counter-id="a" data-delta="1">+1</button>
  <button data-counter-id="a" data-delta="-1">−1</button>
  <button data-counter-id="a" data-delta="5">+5</button>
  <button data-counter-id="a" data-delta="-5">−5</button>
  <button data-counter-id="a" data-reset="true">リセット</button>
</div>

<div class="counter" data-counter-id="b">
  <div id="counter-b" class="counter-value">0</div>
  <button data-counter-id="b" data-delta="1">+1</button>
  <button data-counter-id="b" data-delta="-1">−1</button>
  <button data-counter-id="b" data-delta="5">+5</button>
  <button data-counter-id="b" data-delta="-5">−5</button>
  <button data-counter-id="b" data-reset="true">リセット</button>
</div>

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

data-counter-id="a" のように、「どのカウンター用のボタンか」が分かる。
data-delta="1"data-reset="true" で、「何をするボタンか」が分かる。

JavaScript側は、この「ヒント」を読めばよくなります。


カウンターを「IDから取り出せるように」整理する

counterA / counterB をまとめて管理する

4日目では counterA, counterB を別々に持っていましたが、
今日は「IDで引けるように」まとめます。

const counters = {
  a: {
    count: 0,
    el: document.getElementById("counter-a"),
  },
  b: {
    count: 0,
    el: document.getElementById("counter-b"),
  },
};
JavaScript

こうしておくと、

const counter = counters["a"];  // counterA
const counterBRef = counters["b"];  // counterB
JavaScript

のように、「文字列のIDから対応するカウンター」を取れます。

ここでの深掘りポイントは、「カウンターが増えても設計が崩れない」ということです。
c, d, e… と増やしたいときにも、counters に追加するだけで済みます。


共通ロジックはそのままにしておく

changeCounter / resetCounter は昨日のままでOK

昨日作った共通ロジックは、そのまま使えます。

function renderCounter(counter) {
  counter.el.textContent = counter.count;
}

function changeCounter(counter, delta) {
  const next = counter.count + delta;
  if (next < 0) {
    return;
  }
  counter.count = next;
  renderCounter(counter);
}

function resetCounter(counter) {
  counter.count = 0;
  renderCounter(counter);
}
JavaScript

ここで大事なのは、

「ボタンが増えても、このロジック部分は一切触らない」

ということです。

仕様(0未満防止)がある程度固まったら、ロジックは守る。
操作の種類やボタンの数が増えても、
ロジックを分解して増やさない。

これが、バグを増やさないコツです。


ボタンイベントを「一括で」登録する

deltaボタン(+1 / −1 / +5 / −5)の登録

前準備として、「delta を持っているボタン」を全部取ります。

const deltaButtons = document.querySelectorAll("button[data-delta]");
JavaScript

そして、forEach でまとめてイベントを付けます。

deltaButtons.forEach((btn) => {
  btn.addEventListener("click", () => {
    const id = btn.dataset.counterId;     // "a" や "b"
    const delta = Number(btn.dataset.delta);  // 1, -1, 5, -5 など

    const counter = counters[id];
    if (!counter) {
      return;
    }

    changeCounter(counter, delta);
  });
});
JavaScript

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

「どのカウンターか」は data-counter-id で判断。
「いくつ変えるか」は data-delta で判断。
ロジックは changeCounter に完全に任せる。

これで、+1 / −1 / +5 / −5 のボタンが何個あっても、
この1か所のロジックだけで対応できます。

リセットボタンも一括で扱う

同じ発想で、リセットボタンもまとめます。

const resetButtons = document.querySelectorAll("button[data-reset]");

resetButtons.forEach((btn) => {
  btn.addEventListener("click", () => {
    const id = btn.dataset.counterId;
    const counter = counters[id];
    if (!counter) {
      return;
    }
    resetCounter(counter);
  });
});
JavaScript

ここでも、

どのカウンターか → data-counter-id
何をするか → data-reset(存在している=リセット)

という情報を読んで動いています。


「0未満防止」がちゃんと全ボタンに効いているか確認する

+5 / −5 でも 0未満防止が機能する理由

changeCounter を思い出します。

function changeCounter(counter, delta) {
  const next = counter.count + delta;
  if (next < 0) {
    return;
  }
  counter.count = next;
  renderCounter(counter);
}
JavaScript

delta は、+1 でも +5 でも −1 でも −5 でも構いません。
重要なのは、「次の値 next が 0 未満かどうか」だけを見ていることです。

例えば、今の値が 3 のとき。

−1 ボタン → delta = −1 → next = 2 → 更新OK。
−5 ボタン → delta = −5 → next = −2 → 0未満なので return して何もしない。

つまり、「どのボタン経由でも、最終判断は changeCounter がしている」ということです。
これが「0未満防止」を1か所で守る、という設計の強さです。


5日目までの全体像を一枚にしてみる

コードのイメージをまとめて見る

ここまで学んだことを、簡略化した全体像で整理します。

const counters = {
  a: {
    count: 0,
    el: document.getElementById("counter-a"),
  },
  b: {
    count: 0,
    el: document.getElementById("counter-b"),
  },
};

function renderCounter(counter) {
  counter.el.textContent = counter.count;
}

function changeCounter(counter, delta) {
  const next = counter.count + delta;
  if (next < 0) {
    return;
  }
  counter.count = next;
  renderCounter(counter);
}

function resetCounter(counter) {
  counter.count = 0;
  renderCounter(counter);
}

function setup() {
  // 初期表示
  renderCounter(counters.a);
  renderCounter(counters.b);

  // 増減ボタン
  const deltaButtons = document.querySelectorAll("button[data-delta]");
  deltaButtons.forEach((btn) => {
    btn.addEventListener("click", () => {
      const id = btn.dataset.counterId;
      const delta = Number(btn.dataset.delta);
      const counter = counters[id];
      if (!counter) return;
      changeCounter(counter, delta);
    });
  });

  // リセットボタン
  const resetButtons = document.querySelectorAll("button[data-reset]");
  resetButtons.forEach((btn) => {
    btn.addEventListener("click", () => {
      const id = btn.dataset.counterId;
      const counter = counters[id];
      if (!counter) return;
      resetCounter(counter);
    });
  });
}

setup();
JavaScript

ここまで来ると、もう立派な「強化版・複数カウンターアプリ」です。


5日目のまとめと、6日目へのつなぎ

今日やったことを言葉で整理します。

複数ボタン(+1 / −1 / +5 / −5)を、data-delta として一括で扱える形にした。
各ボタンが「どのカウンター」に対応するかを data-counter-id で表現し、イベントの中で判断した。
counters オブジェクトで、「ID→カウンター状態」のマッピングを作り、拡張しやすくした。
0未満防止のロジックを changeCounter に集約し、どのボタン経由でもルールが守られるようにした。
イベント分離を「ボタンごと」から一歩進めて、「ボタンの種類ごと(delta系 / reset系)」に整理した。

6日目以降は、ここから例えば、

カウンターごとに「最大値」「名前」「色」などのプロパティを足す。
カウンターをボタンで“動的に追加”する方向にチャレンジする。
このカウンターアプリを、別のアプリ(例えばポイント管理や在庫管理)の土台として使ってみる。

といった発展が考えられます。

最後にひとつだけ質問を投げます。

今日の中で、「あ、この設計ちょっと気持ちいい」と感じたのはどこでしたか?
data-counter-id / data-delta で一気にボタンを扱えたところか、
changeCounter が全ボタンを守ってくれている安心感か。

その「気持ちいい」と感じたところが、あなたの設計センスの芯です。
そこを意識して、6日目のステップに進んでいきましょう。

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