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

JavaScript
スポンサーリンク

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

7日目のテーマは「この強化版カウンターアプリを、“自分で説明できるレベル”まで腹落ちさせること」です。
新しい機能を増やす日ではなく、「自分は何を設計できるようになったのか」を整理する日です。

今日のゴールはこうです。
複数ボタン・イベント分離・数値管理・0未満防止を「1つの流れ」として理解できる。
コードを見たときに、「どこが何の役割か」を自分の言葉で説明できる。
「こういうカウンターなら自分で設計して書けそう」と思える。

ここまで来れば、「初級編・強化版カウンターアプリ」は完全クリアです。


ここまでで手に入れた“4つの視点”を整理する

複数ボタンの視点

「ボタンは増えても、やることの本質は同じ」

強化版カウンターアプリでは、こういうボタンを扱ってきました。

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

そして、カウンターも A / B のように複数ありました。
でも、よく見ると「ボタンがやること」は、たった2種類しかありません。

数値を増減させる(+1 / −1 / +5 / −5 など)。
リセットする。

この「本質が2種類しかない」という感覚を持てるようになったのが、複数ボタンの大きなポイントです。

HTML 側ではそれをこう表現しました。

<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>

「どのカウンターに対して」「どんな操作(delta / reset)をするのか」を
data-属性として持たせることで、
JavaScript側は「ボタンの種類が増えてもロジックは増えない」形にできました。

イベント分離の視点

「イベントは“入口”であり、ロジックの本体ではない」

JavaScript側のイベント処理は、こうでした。

const deltaButtons = document.querySelectorAll("button[data-delta]");
deltaButtons.forEach((btn) => {
  btn.addEventListener("click", () => {
    const id = btn.dataset.counterId;
    const delta = Number(btn.dataset.delta);
    changeCounter(id, delta);
  });
});

const resetButtons = document.querySelectorAll("button[data-reset]");
resetButtons.forEach((btn) => {
  btn.addEventListener("click", () => {
    const id = btn.dataset.counterId;
    resetCounter(id);
  });
});
JavaScript

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

どのカウンターか(id)を取り出す。
どんな操作か(delta または reset)を取り出す。
共通ロジック(changeCounter / resetCounter)に渡す。

イベントの中には、「本当のロジック」は書いていません。
ただ「誰に・何を頼むか」を決めているだけです。

これが「イベント分離」です。

「イベントはトリガー(きっかけ)」
「ロジックは専用の関数に集約する」

この意識があると、アプリが大きくなってもコードがぐちゃぐちゃになりません。

数値管理の視点

「状態は1か所に集めておく」

カウンターの状態は、こう持っていました。

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

ポイントは3つです。

count:今の数値。
min / max:このカウンターのルール(0未満禁止+上限)。
el:このカウンターの表示場所。

この「1カウンター=状態+ルール+表示要素」という形になっているおかげで、

A は 0〜10、B は 0〜100 のように
カウンターごとの性格の違いを自然に表現できました。

0未満防止(+上限防止)の視点

「ルールは“1つの関数”に閉じ込める」

changeCounter は、こうなっていました。

function changeCounter(counterId, delta) {
  const counter = counters[counterId];
  if (!counter) return;

  const next = counter.count + delta;

  if (next < counter.min || next > counter.max) {
    return;
  }

  counter.count = next;
  renderCounter(counterId);
}
JavaScript

ここに、「数値変更のルール」がすべて集まっています。

0未満防止を「next < counter.min」で表現している。
上限防止を「next > counter.max」で表現している。
ルール違反なら何もせず return する。

これにより、どんなボタン(+1 / −5 / +5 / −10 …)を増やしても、
最後に changeCounter を通る限り、必ずルールが守られます。

「ルールを1か所に閉じ込める」
これは、アプリ設計で非常に重要な考え方です。


画面更新(render)とボタン状態更新をセットで考える

画面の「正しさ」は render から始まる

カウンターの表示は、こう更新していました。

function renderCounter(counterId) {
  const counter = counters[counterId];
  if (!counter) return;

  counter.el.textContent = counter.count;
  updateButtons(counterId);
}
JavaScript

ここでのポイントは2つです。

数値表示(counter.count → 画面)。
ボタン状態更新(押せる / 押せないの切り替え)。

をセットでやっていることです。

「状態(count)が変わったら、renderCounter を呼ぶ」
「renderCounter は、そのカウンターに関係するUIを全部“正しい姿”にする」

という構造にしておくと、

どこから count が変わっても、
最終的に画面は必ず整った状態になります。

押せないボタンを無効化する updateButtons

updateButtons は、こんなイメージでした。

function updateButtons(counterId) {
  const counter = counters[counterId];
  if (!counter) return;

  deltaButtons.forEach((btn) => {
    if (btn.dataset.counterId !== counterId) return;

    const delta = Number(btn.dataset.delta);
    const next = counter.count + delta;

    if (next < counter.min || next > counter.max) {
      btn.disabled = true;
    } else {
      btn.disabled = false;
    }
  });

  resetButtons.forEach((btn) => {
    if (btn.dataset.counterId !== counterId) return;

    if (counter.count === counter.min) {
      btn.disabled = true;
    } else {
      btn.disabled = false;
    }
  });
}
JavaScript

やっていることはこうです。

「このカウンター用のボタン」だけを見る。
「もし押したらどうなるか(next)」を計算する。
next が min〜max の範囲外なら、ボタンを無効化(disabled = true)。
リセットボタンは、すでに min なら無効化(リセットしても変わらないから)。

ロジックとしてもルールを守り、
UIとしても「今は押せないよ」と教えてあげる。

この両方が揃っているのが、6日目までに作った「こだわりの強化版カウンター」です。


7日目のミニ課題:「自分の言葉で設計を説明してみる」

ここからは、あえてコードではなく「言葉」で設計をまとめてみます。
次の3つを、自分なりに説明できるか考えてみてください。

1. なぜ counters という形にしたのか

考え方の例です。

「カウンターが増えるかもしれないから、
IDをキーにして、カウンターをオブジェクトでまとめておいたほうが拡張しやすい」。

「1つのカウンターの中に、count / min / max / el を全部入れておくと、
そのカウンターに関する情報が“ひとかたまり”になって分かりやすい」。

こんなふうに、自分の感覚で言葉にしてみてください。

2. なぜ changeCounter / resetCounter / renderCounter を分けたのか

これも、考え方の例です。

「changeCounter は“数値を変える+ルールを守る”役」。
「resetCounter は“min に戻す”役」。
「renderCounter は“画面とボタン状態を最新にする”役」。

それぞれの関数が「責任(役割)」を持っていて、
混ざらないように分けている、という説明ができると理想的です。

3. なぜイベントの中にはロジックを書かないのか

考え方の例です。

「イベントの中にロジックを書くと、ボタンが増えたときに似たようなコードが増えて読みにくくなる」。
「イベントは、“どのカウンターに何をさせるか”だけ決めて、
本当のロジックは関数に任せたほうが、後で直しやすい」。

こういう感覚が持てていると、
これから作る別のアプリでも、自然と「イベント分離」を選べるようになります。


7日目のまとめと、これからの一歩

7日目は、「新しいテクニックを覚える日」ではなく、
「ここまで自分が身につけた設計を、自分の中で言語化する日」でした。

あなたが今できることを、あえて一言でまとめると、

「複数のボタンと複数のカウンターがあっても、
イベント分離と数値管理のルールを使って、
壊れないカウンターアプリを設計できる」

という状態です。

最後にひとつだけ、ちゃんと聞きたいことがあります。

この7日間で作ってきたカウンターアプリを、
もし「別の用途」に転用するとしたら、あなたは何に使ってみたいですか?

ポイント管理、在庫数、ゲームのライフ、勉強時間の回数…
何でもいいんです。

「これ、こういう場面でも使えるな」と思えた瞬間、
あなたの中で“ただのサンプル”が“自分の道具”に変わります。

その「使い道」が浮かんだら、
そこから先の設計も、きっとあなたらしく育てていけます。

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