VueJS 速習チュートリアル

Vue v-model

通常の JavaScript と比較して、Vue ではフォームの扱いが非常に簡単です。その理由は、v-model ディレクティブがすべてのタイプの入力要素に対して統一された方法で接続するためです。

v-model は、入力要素の value 属性と Vue インスタンス 内のデータ値(Data Value)との間にリンクを作成します。入力内容を変更するとデータが更新され、データが変更されると入力内容も更新されます。これを 双方向バインディング(Two-way Binding) と呼びます。

1. 双方向バインディング (Two-way Binding)

前ページのショッピングリストの例で見たように、v-model は双方向バインディングを提供します。つまり、フォームの入力要素が Vue インスタンス のデータを更新し、データの変更が入力要素に即座に反映されることを意味します。

以下の例で、v-model による双方向バインディングの挙動を確認しましょう。

1.1 実装例:テキスト入力の同期

入力フィールドに文字を書き込むと、Vue のデータプロパティの値が更新されるのがわかります。

<div id="app">
  <input type="text" v-model="inpText">
  <p> 入力されたテキスト: {{ inpText }} </p>
</div>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
  const app = Vue.createApp({
    data() {
      return {
        inpText: '初期テキスト'
      }
    }
  })
  app.mount('#app')
</script>

注釈: v-model の双方向バインディング機能は、実際には v-bind:valuev-on:input の組み合わせで実現可能です。

  • v-bind:value:Vue インスタンス のデータから入力要素を更新する
  • v-on:input:入力要素から Vue インスタンス のデータを更新する しかし、v-model を使う方がはるかにシンプルで記述も容易なため、通常はこちらを使用します。

2. 動的なチェックボックスの実装

前ページのショッピングリストに、アイテムが「重要(Important)」かどうかをマークするためのチェックボックスを追加してみましょう。

チェックボックスの横には、現在の「重要」ステータス(true または false)を動的に反映するテキストを表示します。ユーザーとのインタラクションを向上させるために v-model を活用します。

必要な要素:

  • Vue インスタンス 内の important という名称の ブーリアン(Boolean) 値。
  • ユーザーが重要度をチェックするためのチェックボックス。
  • ステータスを視覚化するための動的なフィードバックテキスト。

2.1 実装例:チェックボックスの状態同期

チェックボックスの入力値が、即座にテキストに反映される例です。

<div id="app">
  <form>
    <p>
      重要なアイテムですか?
      <label>
        <input type="checkbox" v-model="important">
        ステータス: {{ important }}
      </label>
    </p>
  </form>
</div>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
  const app = Vue.createApp({
    data() {
      return {
        // 初期値は false
        important: false
      }
    }
  })
  app.mount('#app')
</script>

3. ショッピングリストへの統合

この動的な機能を、実際のショッピングリストの例に組み込んでみましょう。

3.1 実装例:重要度フラグ付きリスト

アイテムを追加する際に、重要かどうかを選択できるようにします。

<div id="app">
  <form v-on:submit.prevent="addItem">
    <p>アイテムを追加</p>
    <p>アイテム名: <input type="text" required v-model="itemName"></p>
    <p>数量: <input type="number" v-model="itemNumber"></p>
    <p>
      重要?
      <label>
        <input type="checkbox" v-model="itemImportant">
        {{ itemImportant }}
      </label>
    </p>
    <button type="submit">アイテムを追加</button>
  </form>
  <hr>
  <p>ショッピングリスト:</p>
  <ul>
    <li v-for="item in shoppingList">
      {{ item.name }}, 数量: {{ item.number }}, 重要: {{ item.important }}
    </li>
  </ul>
</div>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
  const app = Vue.createApp({
    data() {
      return {
        itemName: null,
        itemNumber: null,
        itemImportant: false,
        shoppingList: [
          { name: 'トマト', number: 5, important: false }
        ]
      }
    },
    methods: {
      addItem() {
        let item = {
          name: this.itemName,
          number: this.itemNumber,
          important: this.itemImportant
        }
        this.shoppingList.push(item)
        // フォームのリセット
        this.itemName = null
        this.itemNumber = null
        this.itemImportant = false
      }
    }
  })
  app.mount('#app')
</script>

4. リスト内のアイテムを「完了」としてマークする

次に、追加したアイテムを「見つけた(Found)」としてマークできる機能を追加します。

必要なロジック:

  1. リストアイテムがクリックに反応するようにする。
  2. クリックされたアイテムのステータスを found に変更し、CSS を使って視覚的に打ち消し線を引く。

ここでは、未完了のリストと完了済みのリストの2つを作成します。すべてのアイテムを両方のリストに入れ、v-show ディレクティブと found プロパティを使って、どちらのリストに表示するかを動的に制御します。

4.1 実装例:インタラクティブな購入管理

アイテムをクリックするとリスト間を移動します。間違えてクリックした場合は、もう一度クリックすれば未完了リストに戻せます。

<div id="app">
  <form v-on:submit.prevent="addItem">
    <p>アイテムを追加</p>
    <p>アイテム名: <input type="text" required v-model="itemName"></p>
    <p>数量: <input type="number" v-model="itemNumber"></p>
    <p>
      重要?
      <label>
        <input type="checkbox" v-model="itemImportant">
        {{ itemImportant }}
      </label>
    </p>
    <button type="submit">アイテムを追加</button>
  </form>

  <p><strong>買うべきもの(クリックで完了):</strong></p>
  <ul id="ulToFind">
    <li v-for="item in shoppingList"
        v-bind:class="{ impClass: item.important }"
        v-on:click="item.found=!item.found"
        v-show="!item.found">
        {{ item.name }}, {{ item.number }}個
    </li>
  </ul>

  <p><strong>購入済み:</strong></p>
  <ul id="ulFound">
    <li v-for="item in shoppingList"
        v-bind:class="{ impClass: item.important }"
        v-on:click="item.found=!item.found"
        v-show="item.found"
        style="text-decoration: line-through; color: gray;">
        {{ item.name }}, {{ item.number }}個
    </li>
  </ul>
</div>

<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
<script>
  const app = Vue.createApp({
    data() {
      return {
        itemName: null,
        itemNumber: null,
        itemImportant: false,
        shoppingList: [
          { name: 'トマト', number: 5, important: false, found: false }
        ]
      }
    },
    methods: {
      addItem() {
        let item = {
          name: this.itemName,
          number: this.itemNumber,
          important: this.itemImportant,
          found: false
        }
        this.shoppingList.push(item)
        this.itemName = null
        this.itemNumber = null
        this.itemImportant = false
      }
    }
  })
  app.mount('#app')
</script>

5. v-model を使用した動的なフォーム制御

v-model を使えば、フォーム自体の構成を動的に変化させることも可能です。例えば、レストランの注文フォームで「ドリンク」を選択したときだけ、ドリンクの選択肢を表示するといった制御です。

5.1 実装のポイント

  • カテゴリ(ディナー、ドリンク、デザート)を選択するための ラジオボタン(Radio-buttons) を用意。
  • カテゴリが選択されると、そのカテゴリに属するアイテムの ドロップダウンメニュー(Dropdown menu) が表示される。
  • アイテムを選択すると画像が表示され、数量を選んで注文に追加できる。
  • アイテムが追加されると、フォームは初期状態にリセットされる。

このように v-modelv-show を組み合わせることで、最初からすべてのメニューを提示するよりもユーザーにとって使いやすく、洗練された インターフェース を構築できます。