0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Day 6: 実践:シンプルなカウンターアプリを作る

Last updated at Posted at 2025-12-05

この記事は「構造パスで作るシンプルなフロントエンドフレームワーク(Structive)」Advent Calendarの6日目です。
Structiveについて詳しくはこちらより

前回のおさらい

5日目では、Proxyと構造パスを使った仮想DOMなしのリアクティビティを学びました。今日は、Week 1の締めくくりとして、実際に手を動かしてカウンターアプリを作ります。

今日作るもの

シンプルなカウンターアプリを、段階的に機能を追加しながら作っていきます:

  1. 基本のカウンター - 増減ボタン
  2. ステップ機能 - 増減の幅を変更
  3. リセット機能 - 初期値に戻す
  4. 履歴表示 - 変更履歴を記録

セットアップ

まず、基本的なHTMLファイルを用意します:

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>Counter App</title>
  <script type="importmap">
    {
      "imports": {
        "@components/app-main": "./counter.st.html"
      }
    }
  </script>
  <script type="module" src="path/to/framework.js"></script>
</head>
<body>
  <app-main></app-main>
</body>
</html>

Step 1: 基本のカウンター

最もシンプルなカウンターから始めましょう。

counter.st.html

<template>
  <div class="counter">
    <h1>カウンター</h1>
    <div class="display">
      <span class="count">{{ count }}</span>
    </div>
    <div class="buttons">
      <button data-bind="onclick:decrement">-</button>
      <button data-bind="onclick:increment">+</button>
    </div>
  </div>
</template>

<style>
  .counter {
    max-width: 400px;
    margin: 2em auto;
    padding: 2em;
    border: 2px solid #333;
    border-radius: 8px;
    text-align: center;
  }
  .display {
    margin: 2em 0;
  }
  .count {
    font-size: 3em;
    font-weight: bold;
    color: #007bff;
  }
  .buttons button {
    font-size: 1.5em;
    padding: 0.5em 1.5em;
    margin: 0 0.5em;
    cursor: pointer;
  }
</style>

<script type="module">
export default class {
  count = 0;
  
  increment() {
    this.count += 1;
  }
  
  decrement() {
    this.count -= 1;
  }
}
</script>

コードの解説

状態の定義:

count = 0;
  • カウンターの初期値は0
  • クラスのプロパティとして宣言するだけ

UI(テンプレート):

<span class="count">{{ count }}</span>
  • {{ count }}で状態の値を表示
  • 状態が変わると自動的に更新される

イベントハンドラ:

<button data-bind="onclick:increment">+</button>
  • data-bind="onclick:increment"でクリックイベントをバインド
  • incrementメソッドが呼ばれる

メソッド:

increment() {
  this.count += 1;
}
  • 状態を直接変更するだけ
  • Proxyが変更を検知してUIを更新

動作確認

このコードで以下が実現されています:

  • +ボタンでカウントが増える
  • -ボタンでカウントが減る
  • ✅ 数値がリアルタイムで表示される

Step 2: ステップ機能の追加

増減の幅を変更できるようにします。

counter.st.html(更新版)

<template>
  <div class="counter">
    <h1>カウンター</h1>
    
    <!-- ステップの設定 -->
    <div class="step-control">
      <label>
        ステップ: 
        <input 
          type="number" 
          data-bind="valueAsNumber:step"
          min="1"
        >
      </label>
    </div>
    
    <div class="display">
      <span class="count">{{ count }}</span>
    </div>
    
    <div class="buttons">
      <button data-bind="onclick:decrement">- {{ step }}</button>
      <button data-bind="onclick:increment">+ {{ step }}</button>
    </div>
  </div>
</template>

<style>
  /* 既存のスタイル + 追加 */
  .step-control {
    margin-bottom: 1em;
  }
  .step-control input {
    width: 60px;
    padding: 0.3em;
    font-size: 1em;
  }
</style>

<script type="module">
export default class {
  count = 0;
  step = 1;  // 追加
  
  increment() {
    this.count += this.step;  // stepを使う
  }
  
  decrement() {
    this.count -= this.step;  // stepを使う
  }
}
</script>

追加された機能

新しい状態:

step = 1;
  • 増減の幅を管理

双方向バインディング:

<input type="number" data-bind="valueAsNumber:step" min="1">
  • valueAsNumberで数値として自動変換
  • ユーザーが入力するとstepが更新される
  • stepが変わるとinputの値も更新される

動的なボタンラベル:

<button data-bind="onclick:increment">+ {{ step }}</button>
  • ボタンに現在のステップ値を表示
  • stepが変わるとボタンのラベルも自動更新

Step 3: リセット機能の追加

カウントを初期値に戻す機能を追加します。

counter.st.html(更新版)

<template>
  <div class="counter">
    <h1>カウンター</h1>
    
    <div class="step-control">
      <label>
        ステップ: 
        <input 
          type="number" 
          data-bind="valueAsNumber:step"
          min="1"
        >
      </label>
    </div>
    
    <div class="display">
      <span class="count">{{ count }}</span>
    </div>
    
    <div class="buttons">
      <button data-bind="onclick:decrement">- {{ step }}</button>
      <button data-bind="onclick:reset">リセット</button>
      <button data-bind="onclick:increment">+ {{ step }}</button>
    </div>
  </div>
</template>

<script type="module">
const INITIAL_COUNT = 0;

export default class {
  count = INITIAL_COUNT;
  step = 1;
  
  increment() {
    this.count += this.step;
  }
  
  decrement() {
    this.count -= this.step;
  }
  
  reset() {
    this.count = INITIAL_COUNT;
  }
}
</script>

ポイント

定数の活用:

const INITIAL_COUNT = 0;
  • 初期値を定数化
  • リセット時に同じ値に戻せる

リセットメソッド:

reset() {
  this.count = INITIAL_COUNT;
}
  • 状態を初期値に戻すだけ
  • UIは自動的に更新される

Step 4: 履歴表示の追加

変更履歴を記録して表示します。

counter.st.html(完成版)

<template>
  <div class="counter">
    <h1>カウンター</h1>
    
    <div class="step-control">
      <label>
        ステップ: 
        <input 
          type="number" 
          data-bind="valueAsNumber:step"
          min="1"
        >
      </label>
    </div>
    
    <div class="display">
      <span class="count">{{ count }}</span>
    </div>
    
    <div class="buttons">
      <button data-bind="onclick:decrement">- {{ step }}</button>
      <button data-bind="onclick:reset">リセット</button>
      <button data-bind="onclick:increment">+ {{ step }}</button>
    </div>
    
    <!-- 履歴表示 -->
    <div class="history">
      <h2>履歴</h2>
      <ul>
        {{ for:history }}
          <li>{{ history.*.message }}</li>
        {{ endfor: }}
      </ul>
      <button data-bind="onclick:clearHistory">履歴をクリア</button>
    </div>
  </div>
</template>

<style>
  /* 既存のスタイル + 追加 */
  .history {
    margin-top: 2em;
    padding-top: 2em;
    border-top: 1px solid #ccc;
  }
  .history ul {
    list-style: none;
    padding: 0;
    max-height: 200px;
    overflow-y: auto;
    text-align: left;
  }
  .history li {
    padding: 0.5em;
    border-bottom: 1px solid #eee;
  }
</style>

<script type="module">
const INITIAL_COUNT = 0;

export default class {
  count = INITIAL_COUNT;
  step = 1;
  history = [];
  
  increment() {
    this.count += this.step;
    this.addHistory(`+${this.step}${this.count}`);
  }
  
  decrement() {
    this.count -= this.step;
    this.addHistory(`-${this.step}${this.count}`);
  }
  
  reset() {
    this.count = INITIAL_COUNT;
    this.addHistory(`リセット → ${this.count}`);
  }
  
  addHistory(message) {
    const timestamp = new Date().toLocaleTimeString();
    this.history = this.history.concat({
      message: `[${timestamp}] ${message}`
    });
  }
  
  clearHistory() {
    this.history = [];
  }
}
</script>

新機能の解説

履歴の状態:

history = [];
  • 配列で履歴を管理

履歴への追加:

addHistory(message) {
  const timestamp = new Date().toLocaleTimeString();
  this.history = this.history.concat({
    message: `[${timestamp}] ${message}`
  });
}
  • concatで新しい配列を作成(重要!)
  • Day 5で学んだ通り、配列全体を置き換えることでProxyが検知

forループでの表示:

{{ for:history }}
  <li>{{ history.*.message }}</li>
{{ endfor: }}
  • history配列の各要素を表示
  • ワイルドカード*で各アイテムにアクセス

各メソッドから呼び出し:

increment() {
  this.count += this.step;
  this.addHistory(`+${this.step}${this.count}`);  // 履歴を記録
}

完成したアプリの動作

これで以下の機能を持つカウンターアプリが完成しました:

  1. 基本機能: カウントの増減
  2. ステップ調整: 増減幅の変更
  3. リセット: 初期値に戻す
  4. 履歴表示: 操作の記録
  5. 履歴クリア: 履歴の削除

コードの特徴

このアプリを作るのに必要だったのは:

状態(3つのプロパティ):

count = INITIAL_COUNT;
step = 1;
history = [];

メソッド(5つ):

increment()
decrement()
reset()
addHistory()
clearHistory()

ボイラープレートはゼロ:

  • useState不要
  • setter関数不要
  • useEffect不要
  • イベントハンドラのラッパー関数不要

構造パスの利点の実感

このアプリを作って、以下を実感できたはずです:

1. シンプルなコード

// これだけでUIが更新される
this.count += this.step;

2. 明確な依存関係

<!-- このUIは count に依存していることが一目瞭然 -->
<span class="count">{{ count }}</span>

3. 双方向バインディングの自然さ

<!-- 自動的に双方向にバインドされる -->
<input type="number" data-bind="valueAsNumber:step">

4. 配列の扱いやすさ

// concat で新しい配列を作るだけ
this.history = this.history.concat(newItem);

まとめ

今日は、実際に手を動かしてカウンターアプリを作りました:

学んだこと:

  • SFCの基本構造(template、style、script)
  • 状態とUIのバインディング
  • イベントハンドラの定義
  • 双方向バインディング
  • 配列の扱い方(concat)
  • forループでのリスト表示

構造パスの実践的な利点:

  • コード量が少ない
  • 理解しやすい
  • メンテナンスしやすい
  • 機能追加が簡単

次回予告:
明日は、今週のまとめをやります。


次回: Day 7「今週のまとめ」

カウンターアプリを作ってみて、感想や質問があればコメントでぜひ!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?