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

JavaScript
スポンサーリンク

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

6日目のテーマは「“仕様のこだわり”を足しても、設計が崩れないカウンターを育てること」です。
ここまでで、複数カウンター・複数ボタン・イベント分離・0未満防止は一通りできました。

今日は、同じテーマ(複数ボタン / イベント分離 / 数値管理 / 0未満防止)を、もう一段深くします。

カウンターごとに「設定(上限・下限・名前)」を持たせる。
その設定を changeCounter に組み込んで、ロジックをさらに“賢く”する。
「押せないときはボタンも見た目でわかる」UI側の工夫も考える。

コードを長くするのではなく、“設計の密度”を少し上げていきます。


まずは5日目までを「設計図」として整理し直す

counters オブジェクトで「カウンター一覧」を管理していた

5日目の時点で、カウンターはこう管理していました。

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

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

ひとつひとつのカウンターが「オブジェクト」としてまとまっている。
ID(”a”, “b”)から対応するカウンターを取り出せる。

この形があるからこそ、data-counter-id と組み合わせて、
「どのボタンがどのカウンターを動かすか」をシンプルに書けていました。

ボタンは「何をするか」を data-属性で持っていた

増減ボタンは、こういうイメージでした。

<button data-counter-id="a" data-delta="1">+1</button>
<button data-counter-id="a" 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>

JavaScript側では、

どのカウンターか → data-counter-id
いくつ変えるか → data-delta

を読み取って、共通ロジック changeCounter(counter, delta) に渡していました。

ここまで来ると、「イベント分離」が

どのボタンから来ても、
やること自体は共通処理に寄せる。

という形で、かなりきれいにできていました。


6日目のテーマ:カウンターごとに「ルール」を持たせる

例:Aは0〜10、Bは0〜100にしたい

ここで、ちょっとした仕様の違いを入れてみます。

カウンターAは、0〜10 の範囲にしたい。
カウンターBは、0〜100 の範囲にしたい。

つまり、「0未満防止」に加えて「上限防止」も入れたい。
しかも、その上限がカウンターごとに違う。

これを、どう設計に落とすかを考えます。

counters に「設定」を足す

さっきの counters を少しだけ拡張します。

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

これで、

counter.min → このカウンターの最小値
counter.max → このカウンターの最大値

という情報を持てるようになりました。

ここでの大事なポイントは、

「ルール(制約)も状態と同じく、カウンターごとに持たせる」

という発想です。
ルールをグローバル変数にバラバラ書くのではなく、
「そのカウンターの特性」としてまとめるイメージです。


changeCounter を「min / max 対応版」に育てる

ロジックを1か所に集めたまま、機能を増やす

今までの changeCounter はこうでした。

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

これを、「min / max を見る」形に変えます。

function changeCounter(counter, delta) {
  const next = counter.count + delta;

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

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

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

これで、

A:min = 0, max = 10
B:min = 0, max = 100

という設定がそのまま効くようになります。

ここでの深掘りポイントは、

0未満防止を「next < 0」ではなく「next < counter.min」に変えたこと。
上限防止を「next > counter.max」として追加したこと。

つまり、「制約条件」を数字に固定せず、
カウンターオブジェクトに持たせた設定を参照する形にした、ということです。


UI側のこだわり:押せないときはボタンも変える

条件ロジックだけでは「ユーザーには見えない」

いまの時点で、ロジックとしてはこうなっています。

next が min 未満なら何もしない。
next が max 超えでも何もしない。

動作としては正しいですが、ユーザー目線だと

「押しても何も起きない」

という状態になります。
「なぜ何も起こらないのか」が画面から分からないのは、少し不親切です。

そこで、「押しても意味がないときはボタンを押せない見た目にする」という工夫を入れてみます。

ボタンの enabled / disabled を更新する関数を作る

カウンターの表示と同じように、「ボタンの状態も render に含める」という設計にします。

例えば、あるカウンター a に対応するボタンたちをこう取っておきます。

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

その上で、「特定のカウンターに紐づくボタン」を探して状態を変える関数を作ります。

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

    // リセットボタンを無効にするかどうかは好み。
    // 例えば、count がすでに min なら無効にするなどもできる。
    if (counter.count === counter.min) {
      btn.disabled = true;
    } else {
      btn.disabled = false;
    }
  });
}
JavaScript

ここでの重要ポイントは、

「このカウンターのボタンだけを見る」ために counterId を使ってフィルタしていること。
「もし押したらどうなるか(next)」を計算して、その結果が min / max の範囲外なら disabled にしていること。

つまり、changeCounter と同じルールを UI にも反映している状態です。

renderCounter から updateButtons を呼び出す

renderCounter を、表示だけでなくボタン状態の更新も担うようにします。

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

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

changeCounter も、counter ではなく id から呼び出す形に少しだけ変えると、
設計が揃ってきます。

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

ここでの深掘りポイントは、

ロジック(changeCounter)と UI(renderCounter, updateButtons)が、「同じルール」を共有していること。
「押せるかどうか」の視点でも、「実際に反映するかどうか」の視点でも、min / max を見ていること。

という二重チェックがきれいに設計されている点です。


イベント側はますますシンプルになる

クリック時は「id と delta を渡すだけ」

ボタンのクリック処理は、かなりシンプルになります。

deltaButtons.forEach((btn) => {
  btn.addEventListener("click", () => {
    const id = btn.dataset.counterId;
    const delta = Number(btn.dataset.delta);
    changeCounter(id, delta);
  });
});

resetButtons.forEach((btn) => {
  btn.addEventListener("click", () => {
    const id = btn.dataset.counterId;
    resetCounter(id);
  });
});
JavaScript

resetCounter も id ベースにそろえます。

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

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

ここでの設計ポイントは、

イベント側は「id と delta(またはリセット)」を伝えるだけ。
本当のルールや状態の変更は、すべて counters / changeCounter / resetCounter / renderCounter / updateButtons が担っている。

という役割分担です。


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

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

カウンターごとに min / max を持たせ、「0未満防止」を「min / max という設定」に一般化した。
changeCounter に min / max チェックを組み込み、ボタンの種類が増えても一貫したルールで動くようにした。
updateButtons で「押したらどうなるか」を計算し、押せないボタンは disabled にすることで、UI側にもルールを反映した。
renderCounter に「数値表示+ボタン状態更新」をまとめ、状態が変わるたびに画面全体が“正しい姿”になるようにした。
イベント側では「id と delta を渡すだけ」に徹し、ロジックを一切持たないスッキリしたコードにできた。

7日目は、この強化版カウンターアプリを「自分の頭の中の設計図」としてまとめる日になります。

なぜ counters という形にしたのか。
なぜ changeCounter / renderCounter / updateButtons に分けたのか。
なぜ data-counter-id / data-delta を使ったのか。

これらを“自分の言葉”で説明できるようになると、
次に作るどんな小さなアプリでも、この設計が土台として使えるようになります。

最後にひとつ、あなたに聞きたい。

今日の中で、「ここまでやるとちょっと気持ちいいな」と感じたのはどこでしたか?
min / max をカウンターごとに持たせた瞬間か、押せないボタンがグレーアウトしたときか。
その「気持ちいい」の感覚が、あなたと“設計のこだわり”をつなぐポイントです。
そこを大事にしたまま、7日目の仕上げに進んでいきましょう。

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