Lesson 4

ディレクティブ

Lesson 4 Chapter 1
ディレクティブとは

Lesson 3 でも少し触れましたが、この Lesson 4 では、ディレクティブについてより詳しく学習していきます。

1. ディレクティブとは

以下はディレクティブを使用したテンプレートの例です。

<div id="app">
  <div>{{ message }}</div>
  <div><input v-bind:value="message" v-on:input="inputMessage"/></div>
  <p>
    <button v-on:click="switchMessageDisplay">表示・非表示</button>
    <span v-if="isShowMessage"> こんにちは</span>
  </p>
  <ul>
    <li v-for="user in users" v-bind:key="user.id">{{ user.name }} {{ user.age }} 歳</li>
  </ul>
</div>
全てのコードを表示

このサンプルコードは、あくまで見本です。自身でコードを書かなくとも大丈夫です。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>{{ message }}</div>
    <div><input v-bind:value="message" v-on:input="inputMessage"/></div>
    <p>
      <button v-on:click="switchMessageDisplay">表示・非表示</button>
      <span v-if="isShowMessage"> こんにちは</span>
    </p>
    <ul>
      <li v-for="user in users" v-bind:key="user.id">{{ user.name }} {{ user.age }} 歳</li>
    </ul>
  </div>

  <script>
    const { createApp, ref } = Vue
    // setup 関数で記述
    createApp({
      setup() {
        const message = ref('Hello World!')
        const isShowMessage = ref(true)
        const users = [
          {
            id: 1,
            name: '山田太郎',
            age: 27
          },
          {
            id: 2,
            name: '田中花子',
            age: 32
          },
          {
            id: 3,
            name: '鈴木三郎',
            age: 21
          }
        ]
        const switchMessageDisplay = () => {
          isShowMessage.value = !isShowMessage.value
        }
        const inputMessage = (event) => {
          message.value = event.target.value
        }
        return {
          message,
          isShowMessage,
          users,
          switchMessageDisplay,
          inputMessage
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

上記テンプレート内にある、v-bindv-onv-ifv-for のように v- ではじまる Vue 特有の属性を「ディレクティブ」と呼びます。
ディレクティブは、それぞれ固有の機能を持ち、値が変化したときにリアクティブにデータを伝達する役割を持っています。

2. 主なディレクティブの一覧

Vue で使用できるディレクティブには、次のようなものがあります(公式サイト 参照)。

No ディレクティブ 説明
1 v-text 要素の textContent を更新する(指定したテキストが表示される)。
2 v-html 要素の innerHTML を更新する(指定した HTML が表示される)。
3 v-bind HTML 属性等をバインドする。
(省略記法: v-bind:属性名:属性名 と省略できる)
4 v-on クリック等のアクションに対するイベント処理を行う。
(省略記法: v-on:イベント名@イベント名 と省略できる)
5 v-show 要素の表示・非表示を制御する。
6 v-if,v-else-if,v-else 条件分岐を行う。
7 v-for ループ処理を行う。
8 v-model 双方向バインディングを行う。
9 v-slot slots(スロット)に名前を付ける。
10 v-pre 要素内のテキストをそのまま表示する。
11 v-cloak コンパイル完了まで要素に残る。
12 v-once 要素を 1 度だけ描画する。
13 v-memo 不要な再レンダリングを省略する。

No 1 の v-text、No 2 の v-html については、既に Lesson 3 で学習済みですので、このレッスンでは、No 3 の v-bind 以降について学習していきます。

Lesson 4 Chapter 2
HTML属性のバインディング(v-bind)

このチャプターでは、v-bind ディレクティブについて学習していきます。

1. v-bind ディレクティブの概要

v-bind ディレクティブは「HTML テンプレート側の id や class などの属性等」を「JavaScript 側の変数(プロパティ)」にバインディングするものです。
単純に HTML の属性だけでなく、コンポーネントの props などもバインディングすることができます(※)。

props とは

props は、親コンポーネントから子コンポーネントにデータを渡す際に使用されます。

<ChildComponent v-bind:message="myMessage" />

上記は、親コンポーネントから子コンポーネントに message という props を渡す例です。
今は、このテンプレートの意味は分からなくとも大丈夫です。
Lesson 6 で改めて解説します。

1-1. 基本構文

v-bind ディレクティブの基本構文は、次のようになります。

v-bind:属性名="変数名"

変数名の部分には原則として「単一式」のみ指定できます。ただし、class 属性、style 属性には特別な拡張機能が用意されています(後述)。

1-2. v-bind ディレクティブの対象

v-bind ディレクティブが使用できる対象について簡単に表にまとめると、おおよそ次のとおりです。

No 対象 説明
1 HTML の属性 id,name,for,value など、HTML の属性一般に使用可能
2 class 属性 HTML 属性としてのバインドのほか、拡張機能(オブジェクト構文・配列構文)が使用可能
3 style 属性 HTML 属性としてのバインドのほか、拡張機能(オブジェクト構文・配列構文)が使用可能
4 props コンポーネントの props のバインドもできる

最後の props への v-bind の適用については、コンポーネントの解説(Lesson 6)のところで行います。

以下、実際にコードを書きながら、1 つずつ学習していきましょう。

2. 一般的な HTML 属性の例

一般的な例から見ていきましょう。
まず、<a> タグの href 属性に v-bind ディレクティブを使用してみます。

2-1. シンプルな指定例

新しく lesson4 フォルダを作成し、chapter2-1.html というファイルを追加した上で、次のコードを記載してください。

<div id="app">
  <a v-bind:href="url">{{ url }}</a>
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const url = ref('https://www.google.com/')
      return {
        url
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter2-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <a v-bind:href="url">{{ url }}</a>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const url = ref('https://www.google.com/')
        return {
          url
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

以下、内容を 1 つずつ確認していきます。

① v-bind の使用部分

v-bind ディレクティブの使用箇所は、以下のところです。

<a v-bind:href="url">(略)</a>

v-bind:href="url" の記述で href 属性に変数 url をバインドしています。

② setup 側の指定

テンプレート側で受け取る変数 url は、setup 内で次のように指定しています。

const url = ref('https://www.google.com/')
return {
  url
}

つまり、url の初期値として 'https://www.google.com/' がテンプレート側に渡されます。
この指定により、実際の HTML は、次のようになるはずです。

<a href="https://www.google.com/">(略)</a>

③ ブラウザで表示する

それでは、ブラウザで表示してみましょう。

vue_4-2-1.png

右クリックから「検証」を選択し、開発者用の画面を表示します。
Elements タブから該当の <a> タグ部分を見ると想定どおりの HTML となっていることが確認できます。

vue_4-2-2.png

開発者用の画面を閉じて、リンクをクリックすると Google の検索ページに移動することができます。

vue_4-2-3.png

2-2. バインドした値を切り替える例

次は、リンク URL をボタンで切り替えるようにしてみましょう。
先ほど作成した chapter2-1.html ファイルを次のように修正します。

<div id="app">
  <button v-on:click="changeUrl">Change</button><br />
  <a v-bind:href="url">{{ url }}</a>
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const url = ref('https://www.google.com/')

      const changeUrl = () => {
        if (url.value === 'https://www.google.com/') {
          url.value = 'https://ja.vuejs.org/'
        } else {
          url.value = 'https://www.google.com/'
        }
      }

      return {
        url,
        changeUrl
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter2-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <button v-on:click="changeUrl">Change</button><br />
    <a v-bind:href="url">{{ url }}</a>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const url = ref('https://www.google.com/')

        const changeUrl = () => {
          if (url.value === 'https://www.google.com/') {
            url.value = 'https://ja.vuejs.org/'
          } else {
            url.value = 'https://www.google.com/'
          }
        }

        return {
          url,
          changeUrl
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

修正部分は次のとおりです。

① changeUrl メソッドの追加

以下のところで url を切り替えるメソッドを追加しています。

const changeUrl = () => {
  if (url.value === 'https://www.google.com/') {
    url.value = 'https://ja.vuejs.org/'
  } else {
    url.value = 'https://www.google.com/'
  }
}

url が初期値の 'https://www.google.com/' であれば、'https://ja.vuejs.org/'(Vue の公式ページ)に切り替え、そうでなければ初期値の 'https://www.google.com/' に戻すという単純なメソッドです。

② return に changeUrl メソッドを追加

テンプレート側で changeUrl メソッドが使えるように return にメソッドを追加しました。

return {
  url,
  changeUrl
}

③ テンプレートにボタンを追加

テンプレート側で changeUrl メソッドを実行できるボタンを追加しています。

<button v-on:click="changeUrl">Change</button><br />

v-on:click="changeUrl" の部分は、クリックしたら changeUrl メソッドを実行するという指定になります(この v-on については次のチャプターで改めて解説します)。

④ ブラウザで動作確認する

コードの修正が終わりましたら、ブラウザで動作確認してみましょう。
既にブラウザを開いている場合は再読込み(リロード)をしてみてください。

次の画面が表示されたら「Change」ボタンをクリックしてみましょう。

vue_4-2-4.png

ボタンを押すたびに URL がリアクティブに変化します。
URL 表示が次のように 'https://ja.vuejs.org/' に切り替わった状態で、リンクをクリックしてみてください。

vue_4-2-5.png

次のように、Vue の公式ページが表示されれば成功です。

vue_4-2-6.png

3. class 属性のバインディング

次に、HTML の class 属性のバインディングについて学習していきます。
基本的なところは、先に見た href 属性と同じですが、v-bindclass 属性で使用するときには「特別な拡張機能」が提供されています。

No 機能 説明
1 一般的な構文 他の HTML の属性と同様に変数でクラス名をバインドする
2 オブジェクト構文(拡張機能) { class 名: 適用の可否を示す真偽値 } のオブジェクト形式でクラス名をバインドする
3 配列構文(拡張機能) 配列形式で複数のクラス名をバインドする

以下、実際にコードを書きながら確認していきましょう。

3-1. 一般的な構文

最初に、単純にクラス名を変数で指定する例です。
chapter2-2.html というファイルを追加した上で、次のコードを記載してください。

<style>
  .red {
    color: red
  }
</style>

<div id="app">
  <div v-bind:class="className">こんにちは</div>
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const className = ref('red')
      return {
        className
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter2-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <style>
    .red {
      color: red
    }
  </style>

  <div id="app">
    <div v-bind:class="className">こんにちは</div>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const className = ref('red')
        return {
          className
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を確認していきましょう。

① CSS の指定

今回は、class 属性の確認をするため、<style> タグを使用して CSS を記載しています。

<style>
  .red {
    color: red
  }
</style>

CSS を HTML ファイル内に記載する場合は、一般的に <head> タグの中に書きますが、ここでは一覧性を優先して <body> タグの中に記載しています。

② v-bind の使用部分

v-bind ディレクティブは、以下のように指定しています。

<div v-bind:class="className">こんにちは</div>

v-bind:class="className"> の記述で class 属性に変数 className をバインドしています。

③ setup 側の指定

テンプレート側で受け取る変数 className は、setup 内で次のように指定しています。

const className = ref('red')
return {
  className
}

以上により、テンプレート側の class 名に red が指定されることになります。

④ ブラウザで表示する

それでは、ブラウザで表示して CSS が適用されているか確認してみましょう。

vue_4-2-7.png

開発者用画面を見ると、クラス名 red が正しくバインドされ、こんにちは の文字が赤くなっていることが確認できます。

3-2. オブジェクト構文を使用して class 属性の適用を切り替える

次に「オブジェクト構文」というものを使用します。
基本的な構文は、次のようになります。

<div v-bind:class="{ クラス名: 真偽値 }"></div>

例えば、v-bind:class="{ active: true }" の場合は、class="active" というようにクラス名が適用され、真偽値が false の場合は「クラス名の指定無し」となります。

なお、複数のクラスを指定する場合は次のようになります。

<div v-bind:class="{ クラス名1: 真偽値1, クラス名2: 真偽値2, ... }"></div>

それでは、実際に「オブジェクト構文」を使用してコードを書いてみましょう。 chapter2-3.html というファイルを追加した上で、次のように記載してください。

<style>
  .active {
    font-weight: bold;
    color: blue;
  }
</style>

<div id="app">
  <button v-on:click="changeActive">Change</button><br />
  <div v-bind:class="{ active: isActive }">こんにちは</div>
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const isActive = ref(false)
      const changeActive = () => isActive.value = !isActive.value
      return {
        isActive,
        changeActive
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter2-3.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <style>
    .active {
      font-weight: bold;
      color: blue;
    }
  </style>

  <div id="app">
    <button v-on:click="changeActive">Change</button><br />
    <div v-bind:class="{ active: isActive }">こんにちは</div>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const isActive = ref(false)
        const changeActive = () => isActive.value = !isActive.value
        return {
          isActive,
          changeActive
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

説明するまでもないかもしれませんが、念のため、コードの内容を確認していきます。

① CSS の指定

以下で active というクラスに「太字」かつ「青文字」を指定しています。

.active {
  font-weight: bold;
  color: blue;
}

② オブジェクト構文

次のところでオブジェクト構文を使用しています。

<div v-bind:class="{ active: isActive }">こんにちは</div>

isActivetrue であればクラス名 active を適用し、 false であれば、クラス名を適用しないことになります。

③ setup 側の指定

isActive の初期値は false とし、メソッド changeActive が実行されるたびに truefalsetrue というように切り替わるようにしています。

const isActive = ref(false)
const changeActive = () => isActive.value = !isActive.value
return {
  isActive,
  changeActive
}

④ ブラウザで表示する

それでは、ブラウザで表示してクラス適用の切り替えを確認してみましょう。
isActive の初期値は false のため、こんにちは の文字にはクラス active は適用されていません。

vue_4-2-8.png

次に Change ボタンをクリックすると、class="active" というようにクラスが適用され、こんにちは の文字が「太字」かつ「青字」に変わることが確認できます

vue_4-2-9.png

(参考)複数のクラスを指定する場合

複数のクラスを指定する場合について、コードの例のみ紹介しておきます。

<div id="app">
  <button v-on:click="changeActive">ChangeActive</button><br />
  <button v-on:click="changeError">ChangeError</button><br />
  <div v-bind:class="{ active: isActive, error: isError }">こんにちは</div>
</div>
全てのコードを表示

動作確認をする場合は、chapter2-4.html というファイルを追加した上で、次のように記載します。

chapter2-4.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <style>
    .active {
      font-weight: bold;
    }
    .error {
      color: red;
    }
  </style>

  <div id="app">
    <button v-on:click="changeActive">ChangeActive</button><br />
    <button v-on:click="changeError">ChangeError</button><br />
    <div v-bind:class="{ active: isActive, error: isError }">こんにちは</div>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const isActive = ref(false)
        const isError = ref(false)
        const changeActive = () => isActive.value = !isActive.value
        const changeError = () => isError.value = !isError.value
        return {
          isActive,
          isError,
          changeActive,
          changeError
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

クラスを 1 つ指定する場合と基本的に変わりはありませんので、ここでは、特に解説は行いません。
ご自身で動作確認をしてみて、複数のクラスを指定する書き方を把握しておいてください。

3-3. 配列構文を使用して class 属性を複数指定する

次に「配列構文」について紹介します。
配列構文は、配列形式で複数のクラスを指定する書き方であり、基本的な構文は次のようになります。

<div v-bind:class="[クラス指定1, クラス指定2 ...]"></div>

角括弧 [] を使用することで、配列形式で複数のクラスを指定することができます。

実際に「配列構文」を使用してコードを書いてみます。 chapter2-5.html というファイルを追加した上で、次のように記載してください。

<style>
  .active {
    font-weight: bold;
  }
  .blue {
    color: blue;
  }
</style>

<div id="app">
  <div v-bind:class="[class1, class2]">こんにちは</div>
</div>

<script>
  Vue.createApp({
    setup() {
      return {
        class1: 'active',
        class2: 'blue'
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter2-5.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <style>
    .active {
      font-weight: bold;
    }
    .blue {
      color: blue;
    }
  </style>

  <div id="app">
    <div v-bind:class="[class1, class2]">こんにちは</div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        return {
          class1: 'active',
          class2: 'blue'
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

指定している内容は単純なものです。以下、コードの内容を確認していきます。

① 配列構文

<div v-bind:class="[class1, class2]">こんにちは</div>

配列構文は、上記の v-bind:class="[class1, class2]" の部分です。
ここで、クラス名として、変数 class1 と変数 class2 の 2 つを受け取ることができます。

② クラス名の指定

変数 class1 と変数 class2 は、setup 関数内で次のように指定しています。

return {
  class1: 'active',
  class2: 'blue'
}

上記 2 つのクラス名の指定により、テンプレートは次のように描画されることになります。

<div class="active blue">こんにちは</div>

③ ブラウザで表示する

それでは、ブラウザで表示してみましょう。
配列構文で指定した 2 つのクラスが適用されているのが確認できると思います。

vue_4-2-10.png

(参考)配列構文内にオブジェクト構文等を使用する

配列構文の中にはクラス名だけでなく、「オブジェクト構文」や「三項演算子」を使用することができます。以下、コードの例を見てみましょう。

<div id="app">
  <button v-on:click="changeActive">ChangeActive</button><br />
  <button v-on:click="changeColor">ChangeColor</button><br />
  <div v-bind:class="[{ active: isActive }, isBlue ? 'blue' : '']">こんにちは</div>
</div>
全てのコードを表示

chapter2-6.html というファイルを追加した上で、次のように記載します。

chapter2-6.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <style>
    .active {
      font-weight: bold;
    }
    .blue {
      color: blue;
    }
  </style>

  <div id="app">
    <button v-on:click="changeActive">ChangeActive</button><br />
    <button v-on:click="changeColor">ChangeColor</button><br />
    <div v-bind:class="[{ active: isActive }, isBlue ? 'blue' : '']">こんにちは</div>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const isActive = ref(false)
        const isBlue = ref(false)
        const changeActive = () => isActive.value = !isActive.value
        const changeColor = () => isBlue.value = !isBlue.value
        return {
          isActive,
          isBlue,
          changeActive,
          changeColor
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

配列構文の 1 つ目は、次のようにオブジェクト構文が使用されています。
言うまでもないですが、isActivetrue であれば、クラス active が追加されます。

{ active: isActive }

配列構文の 2 つ目は、三項演算子が使用されています。

isBlue ? 'blue' : ''

三項演算子の構文は 条件 ? Trueの場合 : Falseの場合 であるので、 isBluetrue であればクラス名 'blue' が適用され、false であれば '' すなわち何も適用されないということになります。

ここでは実行確認はしませんが、ご自身でコードを記載して動作確認をしてみてください。

4. style 属性のバインディング

次に、style 属性のバインディングについて学習していきます。
style 属性にも class 属性と同様に「特別な拡張機能」が提供されています。

No 機能 説明
1 一般的な構文 他の HTML の属性と同様に変数で style 属性をバインドする
2 オブジェクト構文(拡張機能) { プロパティ名: 値 } のオブジェクト形式で style 属性をバインドする
3 配列構文(拡張機能) 配列形式で複数の style 属性をバインドする

以下、実際にコードを書きながら確認していきましょう。

3-1. 一般的な構文

最初に、単純に style 属性を変数で指定する例です。
chapter2-7.html というファイルを追加した上で、次のコードを記載してください。

<div id="app">
  <div v-bind:style="styleText">こんにちは</div>
</div>

<script>
  Vue.createApp({
    setup() {
      return {
        styleText: 'color: red;'
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter2-7.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-bind:style="styleText">こんにちは</div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        return {
          styleText: 'color: red;'
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

以下のように、コードの内容は単純なものです。

① v-bind の使用部分

v-bind ディレクティブは、以下のように指定しています。

<div v-bind:style="styleText">こんにちは</div>

③ setup 側の指定

テンプレート側で受け取る変数 styleText は、setup 内で次のように指定しています。

return {
  styleText: 'color: red;'
}

③ ブラウザで表示する

それでは、ブラウザで表示して style 属性が適用されているか確認してみましょう。

vue_4-2-11.png

開発者用画面を見ると、style 属性に color: red; が適用されていることが確認できます。

3-2. オブジェクト構文を使用して style 属性をバインドする

次に「オブジェクト構文」を使用します。
基本的な構文は、次のようになります。

<div v-bind:style="{ プロパティ名: 値 }"></div>

例えば、v-bind:style="{ color: 'red' }" の場合は、style="color: red;" というように style 属性が適用されます。

複数の style 属性を指定する場合は次のようになります。

<div v-bind:style="{ プロパティ名1: 値1, プロパティ名2: 値2, ... }"></div>

それでは、オブジェクト構文を使用してコードを書いて行きましょう。
chapter2-8.html というファイルを追加した上で、次のように記載してください。

<div id="app">
  <div v-bind:style="{ color: activeColor, fontSize: fontSize }">こんにちは</div>
</div>

<script>
  Vue.createApp({
    setup() {
      return {
        activeColor: 'blue',
        fontSize: '30px'
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter2-8.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-bind:style="{ color: activeColor, fontSize: fontSize }">こんにちは</div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        return {
          activeColor: 'blue',
          fontSize: '30px'
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

以下、コードの内容を確認していきます。

① オブジェクト構文

次のところでオブジェクト構文を使用しています。

<div v-bind:style="{ color: activeColor, fontSize: fontSize }">こんにちは</div>

colorfontSize は CSS のプロパティ名に該当します。 なお、fontSize はキャメルケースで書かれていますが、バインド後はケバブケース(font-size)に変換されます(※)。

② setup 側の指定

テンプレート側で受け取る変数として、次のように指定しています。

return {
  activeColor: 'blue',
  fontSize: '30px'
}

これが、① のオブジェクト構文に適用されると、次のようになるはずです。

<div style="color: blue; font-size: 30px;">こんにちは</div>

キャメルケースとケバブケースの変換

style 属性のオブジェクト構文内で CSS のプロパティ名を指定するときは、キャメルケースで記載するとバインド時にケバブケースに自動変換されます。

v-bind:style="{ backgroundColor: 'white' }" → style="background-color: white;"

もし、ケバブケースで指定する場合は次のようにクォーテーションで囲む必要がありますのでご注意ください。

v-bind:style="{ 'background-color': 'white' }"

③ ブラウザで表示する

それでは、ブラウザで表示して確認してみましょう。
オブジェクト構文で指定したとおりに、style 属性が設定されていることが確認できます。

vue_4-2-12.png

(参考)変数に直接オブジェクト構文を当てはめる

変数に直接オブジェクト構文を当てはめる場合について、コード例のみ紹介しておきます。

<div id="app">
  <div v-bind:style="styleObject">こんにちは</div>
</div>

<script>
  Vue.createApp({
    setup() {
      return {
        styleObject: {
          color: 'blue',
          fontSize: '30px'
        }
      }
    }
  }).mount('#app')
</script>
全てのコードを表示

chapter2-9.html というファイルを追加した上で、次のように記載します。

chapter2-9.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-bind:style="styleObject">こんにちは</div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        return {
          styleObject: {
            color: 'blue',
            fontSize: '30px'
          }
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

実行結果は、先に実行したchapter2-8.html の例と全く同じです。
ご自身で動作確認をしてみてください。

3-3. 配列構文を使用して style 属性を複数指定する

style 属性の v-bind でも配列構文を使用して複数の CSS を指定することができます。

<div v-bind:style="[CSS指定1, CSS指定2 ...]"></div>

実際にコードを書いて確認してみましょう。 chapter2-10.html というファイルを追加した上で、次のように記載してください。

<div id="app">
  <div v-bind:style="[styleObject1, styleObject2]">こんにちは</div>
</div>

<script>
  Vue.createApp({
    setup() {
      return {
        styleObject1: {
          color: 'blue',
          fontSize: '30px'
        },
        styleObject2: {
          fontWeight: 'bold'
        }
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter2-10.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-bind:style="[styleObject1, styleObject2]">こんにちは</div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        return {
          styleObject1: {
            color: 'blue',
            fontSize: '30px'
          },
          styleObject2: {
            fontWeight: 'bold'
          }
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

以下、コードの内容を確認していきます。

① 配列構文

<div v-bind:style="[styleObject1, styleObject2]">こんにちは</div>

配列構文は、上記の v-bind:style="[styleObject1, styleObject2]" の部分です。

② style 属性の指定

setup 関数内で次のようにオブジェクト構文の形式で 2 つの style を指定しています。

return {
  styleObject1: {
    color: 'blue',
    fontSize: '30px'
  },
  styleObject2: {
    fontWeight: 'bold'
  }
}

③ ブラウザで表示する

それでは、ブラウザで表示してみましょう。
配列構文で指定した 2 つの style オブジェクトが連結して適用されているのが確認できると思います。

vue_4-2-13.png

4. v-bind の省略記法

各ディレクティブの v- 接頭辞は、テンプレート内で Vue 独自の属性であることを識別する目印とされています。
ただし、v-bind は使用頻度が高いため、以下のような省略記法が用意されています。

4-1. 省略記法の構文

以下のように、v-bind: の部分は、単に : という記載に省略できます。

<!-- 完全な構文 -->
<a v-bind:href="url"> ... </a>

<!-- 省略記法 -->
<a :href="url"> ... </a>

<!-- 動的引数の省略記法 -->
<a :[key]="url"> ... </a>

最後の例は「動的引数」というものを使用した v-bind の例です。動的引数については次項で説明します。

4-2. 省略記法の具体例

具体的な例を 1 つ見てみましょう。
例えば、先ほどの「配列構文を使用した style 属性」の例(chapter2-10.html)は以下のように v-bind を指定していました。

<div v-bind:style="[styleObject1, styleObject2]">こんにちは</div>

これを省略記法を使用して記述すると、次のようになります。

<div :style="[styleObject1, styleObject2]">こんにちは</div>

以上のように省略記法を使用することでコードの冗長化を抑えることもできます。
ただし、省略記法を使用するか否かは、チームの指針にもよるところですので、状況に合わせて使用するようにしましょう。

5. 動的引数を使用した v-bind

最後に、ディレクティブに動的引数を使用する場合について見ていきます。
今までは、v-bind:class というように属性名は固定値で記述してきましたが、この class の部分を動的に指定することもできます。

5-1. 動的引数の構文

v-bind ディレクティブの属性に動的引数を使用する場合は、次のように記述します。

v-bind:[属性の変数名]="変数名"

属性の変数名 の部分には「単一式」のみ指定できます。

5-2. 動的引数の具体例

それでは、実際に動的引数を使用してコードを書いてみましょう。
chapter2-11.html というファイルを追加した上で、次のように記載します。

<div id="app">
  <a v-bind:[attributename]="url">ここをクリック</a>
</div>

<script>
  Vue.createApp({
    setup() {
      return {
        attributename: 'href',
        url: 'https://www.google.com/'
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter2-11.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <a v-bind:[attributename]="url">ここをクリック</a>
  </div>

  <script>
    Vue.createApp({
      setup() {
        return {
          attributename: 'href',
          url: 'https://www.google.com/'
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

以下、コードの内容を確認していきます。

① 動的引数の使用部分

次のところが動的引数の使用部分です。

<a v-bind:[attributename]="url">ここをクリック</a>

attributename は全て小文字で記載していることに注意してください。
このレッスンで用いている CDN のように、HTML ファイルに直接記述する DOM 内テンプレートを使用する場合、属性名に大文字を使用してもブラウザー側で小文字に変換されてしまう事象があるためです(詳しくは 動的引数の構文上の制約 参照)。

② setup 側の指定

テンプレート側で受け取る変数として、次のように指定しています。

return {
  attributename: 'href',
  url: 'https://www.google.com/'
}

③ ブラウザで表示する

それでは、ブラウザで表示して確認してみましょう。
次のように、属性値が正しく指定されていれば OK です。

vue_4-2-14.png

本チャプターでは v-bind ディレクティブについて学習しました。
多用するディレクティブですので、しっかりと身に着けておくようにしてください。

Lesson 4 Chapter 3
イベントハンドリング(v-on)

このチャプターでは、イベントハンドリング(※)を行う v-on ディレクティブについて学習していきます。

イベントハンドリングとは

イベントハンドリングとは「プログラミングにおいて、特定の事象(イベント)が発生したときに特定の処理(メソッド)を実行するように定めること」をいいます。

例えば、画面上のボタンをクリックしたら注文カートに追加するとか、文字が入力されたら文字数制限をチェックするなど、様々なイベントと処理のパターンがあります。

1. v-on ディレクティブの概要

v-on ディレクティブを使用することで「クリックなどのイベントを監視し、そのイベントが発生したら JavaScript を実行する」というような処理を実現することができます。

1-1. 基本構文

v-on ディレクティブの基本構文は、次のようになります。

v-on:イベント名="メソッド"

見てのとおりですが、イベント名 に指定したイベントが発生すると、メソッド に指定したメソッドが実行されます。

1-2. v-on ディレクティブで使用される主なイベント

v-on ディレクティブで使用される主なイベントを一覧にすると、次のとおりです。

No カテゴリ イベント名 説明
1 マウス click クリックしたとき
2 dblclick ダブルクリックしたとき
3 mousedown マウスボタンを押下したとき
4 mouseup マウスボタンを離したとき
5 mouseover マウスカーソルを当てたとき
6 mousemove マウスカーソルが動いたとき
7 mouseout マウスカーソルが離れたとき
8 キーボード keydown キーを押したとき
9 keypress キーを押し続けているとき
10 keyup キーを離したとき
11 インプット input 値が入力されたとき
12 フォーム select テキストを選択したとき
13 change 要素の値が変更され、フォーカスを失ったとき
14 submit フォーム送信(submit)ボタンが押下されたとき
15 focus フォーカスしたとき
16 blur フォーカスが外れたとき
17 ページ scroll ページがスクロールしたとき

以上のほかにも様々なイベントがあります(DOM イベント などを参照ください)。
このチャプターで全てを紹介することは難しいですので、使用頻度の高いものを優先して紹介していきます。

1-3. メソッドイベントハンドラとインラインメソッドハンドラ

イベント発火時に実行するメソッドの指定は、①メソッド名、または、②JavaScript 式で記述します。
メソッド名で指定する方法を「メソッドイベントハンドラ」といい、JavaScript 式で指定する方法を「インラインメソッドハンドラ」といいます。

① メソッド名で指定(メソッドイベントハンドラ)

メソッド名で指定する場合は次のようになります。
v-on:click="increment" というように、クリック時に increment メソッドが実行されるように指定しています。メソッド名で指定するため末尾の () は不要です。

<div id="app">
  <button v-on:click="increment">クリック</button><br />
  Counter: {{ count }}
</div>

<script>
  Vue.createApp({
    setup() {
      const count = Vue.ref(0)
      const increment = () => count.value++
      return { count, increment }
    }
  }).mount('#app')
</script>
全てのコードを表示

動作確認をする場合は、chapter3-0-1.html というファイルを追加した上で、次のように記載します。

chapter3-0-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <button v-on:click="increment">クリック</button><br />
    Counter: {{ count }}
  </div>

  <script>
    Vue.createApp({
      setup() {
        const count = Vue.ref(0)
        const increment = () => count.value++
        return { count, increment }
      }
    }).mount('#app')
  </script>
</body>
</html>

なお、v-on:click="increment()" というように、メソッド名の末尾に () を付けても動作しますが、increment() という記述は JavaScript 式になるため、分類としては後述の「インラインメソッドハンドラ」となります。

② JavaScript 式で指定(インラインメソッドハンドラ)

JavaScript 式で指定する場合は次のようになります。
v-on:click="count++" というように、直接 JavaScript 式でメソッドを指定しています。

<div id="app">
  <button v-on:click="count++">クリック</button><br />
  Counter: {{ count }}
</div>

<script>
  Vue.createApp({
    setup() {
      const count = Vue.ref(0)
      return { count }
    }
  }).mount('#app')
</script>
全てのコードを表示

動作確認をする場合は、chapter3-0-2.html というファイルを追加した上で、次のように記載します。

chapter3-0-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <button v-on:click="count++">クリック</button><br />
    Counter: {{ count }}
  </div>

  <script>
    Vue.createApp({
      setup() {
        const count = Vue.ref(0)
        return { count }
      }
    }).mount('#app')
  </script>
</body>
</html>

2. マウスイベント

2-1. クリック(click,dblclick)

これまでの学習で既に何度も使用していますが、まず、v-on:click についてコードを書いて確認します。併せて v-on:dblclick も確認します。

それでは、実際にコードを書いてみましょう。
chapter3-1.html というファイルを追加した上で、次のように記載します。

<div id="app">
  <button v-on:click="onClick">クリック</button><br />
  <button v-on:dblclick="onDblclick">ダブルクリック</button>
</div>

<script>
  Vue.createApp({
    setup() {
      const onClick = () => alert('クリックしました!')
      const onDblclick = () => alert('ダブルクリックしました!')
      return {
        onClick,
        onDblclick
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter3-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <button v-on:click="onClick">クリック</button><br />
    <button v-on:dblclick="onDblclick">ダブルクリック</button>
  </div>

  <script>
    Vue.createApp({
      setup() {
        const onClick = () => alert('クリックしました!')
        const onDblclick = () => alert('ダブルクリックしました!')
        return {
          onClick,
          onDblclick
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

以下、コードの内容を確認していきます。

① v-on ディレクティブ指定部分

次の箇所で click イベントおよび dblclick イベントについて v-on を使用しています。

<button v-on:click="onClick">クリック</button><br />
<button v-on:dblclick="onDblclick">ダブルクリック</button>

呼び出されるメソッドは、onClick メソッドおよび onDblclick メソッドです。

② メソッドの指定

setup 関数内で、次のようにメソッドを指定しています。

const onClick = () => alert('クリックしました!')
const onDblclick = () => alert('ダブルクリックしました!')

メソッドが実行されたら、JavaScript の alert() メソッドでダイアログを表示するものです。

③ ブラウザで表示する

それでは、ブラウザで開いてみましょう。

vue_4-3-1.png

クリックボタンをクリックすると「クリックしました!」というダイアログが表示されます。

vue_4-3-2.png

ダブルクリックボタンをダブルクリックすると「ダブルクリックしました!」というダイアログが表示されます。

vue_4-3-3.png

簡単な例ですが、以上のような形で v-on ディレクティブを使用することで、JavaScript のイベント操作ができるようになっています。

右クリックでイベントを発生させる

右クリックでイベントを発生させる場合は、v-on:click の部分を v-on:click.right とすれば OK です。

<button v-on:click.right="onClickRight">右クリック</button>

なお、contextmenu イベントを使用しても同じ結果を得ることができます。

<button v-on:contextmenu="onContextmenu">contextmenu</button>

2-2. マウス操作(mousedown,mouseup,mouseover,mouseout)

次に、クリック操作以外のマウス操作に関するイベントを見ていきましょう。

それでは、実際にコードを書いてみましょう。
chapter3-1.html というファイルを追加した上で、次のように記載します。

<div id="app">
  <div>
    <p
      v-on:mousedown="onMousedown"
      v-on:mouseup="onMouseup"
      v-on:mouseover="onMouseover"
      v-on:mouseout="onMouseout"
      style="background-color: yellow; line-height: 64px;"
    >
      マウス操作確認
    </p>
    <p>{{ message }}</p>
  </div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const message = ref('')
      const onMousedown = () => message.value = 'マウスボタンを押しました'
      const onMouseup = () => message.value = 'マウスボタンを離しました'
      const onMouseover = () => message.value = 'マウスカーソルがホバーしました'
      const onMouseout = () => message.value = 'マウスカーソルが離れました'
      return {
        message,
        onMousedown,
        onMouseup,
        onMouseover,
        onMouseout
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter3-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>
      <p
        v-on:mousedown="onMousedown"
        v-on:mouseup="onMouseup"
        v-on:mouseover="onMouseover"
        v-on:mouseout="onMouseout"
        style="background-color: yellow; line-height: 64px;"
      >
        マウス操作確認
      </p>
      <p>{{ message }}</p>
    </div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const message = ref('')
        const onMousedown = () => message.value = 'マウスボタンを押しました'
        const onMouseup = () => message.value = 'マウスボタンを離しました'
        const onMouseover = () => message.value = 'マウスカーソルがホバーしました'
        const onMouseout = () => message.value = 'マウスカーソルが離れました'
        return {
          message,
          onMousedown,
          onMouseup,
          onMouseover,
          onMouseout
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

以下、コードの内容を確認していきます。

① v-on ディレクティブ指定部分

次の箇所で mousedownmouseupmouseovermouseout の各イベントにつき v-on で指定しています。 横に長くなるため適度のところで折り返しを入れています。

<p
  v-on:mousedown="onMousedown"
  v-on:mouseup="onMouseup"
  v-on:mouseover="onMouseover"
  v-on:mouseout="onMouseout"
  style="background-color: yellow; line-height: 64px;"
>
  マウス操作確認
</p>

mousedown はマウスボタン押下時にイベントが発生します。
mouseup はマウスボタンを離したときにイベントが発生します。
mouseover は要素にマウスカーソルが当たっているときにイベントが発生します。
mouseout は要素からマウスカーソルが外れたときにイベントが発生します。

② メソッドの指定

setup 関数内で、次のようにメソッドを指定しています。

const onMousedown = () => message.value = 'マウスボタンを押しました'
const onMouseup = () => message.value = 'マウスボタンを離しました'
const onMouseover = () => message.value = 'マウスカーソルがホバーしました'
const onMouseout = () => message.value = 'マウスカーソルが離れました'

メソッドが実行されたら、どのイベントが発火(※)したか分かる文言を message に入れて表示するようにしています。

イベントの発火

プログラミングにおいては、イベントが発生することを「イベントの発火」と言います。

③ ブラウザで確認する

それでは、ブラウザで開いてみましょう。

vue_4-3-4.png

黄色の部分にマウスカーソルを重ねると、mouseover イベントが発火します。

vue_4-3-5.png

黄色の部分からマウスカーソルが外れると、mouseout イベントが発火します。

vue_4-3-6.png

黄色の部分でマウスボタンを押下すると、mousedown イベントが発火します。

vue_4-3-7.png

黄色の部分でマウスボタンを離すと、mouseup イベントが発火します。

vue_4-3-8.png

以上のようにマウス操作に関する様々なイベントを v-on で使用することができます。
ここで紹介していないものもありますので、興味があればご自身でお試しください。

3. キーボードイベント

3-1. キーを押す・離す(keydown,keyup)

次はマウスではなく、キーボードに関するイベントです。

早速、コードを書いてみましょう。
chapter3-3.html というファイルを追加した上で、次のように記載します。

<div id="app">
  <div>
    <input v-on:keydown="onKeydown" v-on:keyup="onKeyup" />
    <p>{{ message }}</p>
  </div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const message = ref('')
      const onKeydown = () => message.value = 'キーを押しました'
      const onKeyup = () => message.value = 'キーを離しました'
      return {
        message,
        onKeydown,
        onKeyup,
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter3-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>
      <input v-on:keydown="onKeydown" v-on:keyup="onKeyup" />
      <p>{{ message }}</p>
    </div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const message = ref('')
        const onKeydown = () => message.value = 'キーを押しました'
        const onKeyup = () => message.value = 'キーを離しました'
        return {
          message,
          onKeydown,
          onKeyup,
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を確認していきます。

① v-on ディレクティブ指定部分

次のように keydown イベントおよび keyup イベントを v-on で指定しています。

<input v-on:keydown="onKeydown" v-on:keyup="onKeyup" />

② メソッドの指定

メソッドの指定は、次のとおりです。

const onKeydown = () => message.value = 'キーを押しました'
const onKeyup = () => message.value = 'キーを離しました'

実行したイベントに対応するメッセージが表示されます。

③ ブラウザで表示する

それでは、ブラウザで開いてみましょう。
インプットボックス内で何らかのキーを押下すると keydown イベントが発生し「キーを押しました」というメッセージが表示されます。

vue_4-3-9.png

キーを離すと keyup イベントが発生し「キーを離しました」というメッセージが表示されます。

vue_4-3-10.png

3-2. キー押下時のイベントを取得する

先ほどの例では、キーを押下する(または離す)ときのイベントを受けてメソッドを実行しました。

このときに「どのキーを押したか」などのイベントの情報を取得することができます。
取得されるイベントは次のようなオブジェクト形式であり、一般に「イベントオブジェクト」と呼ばれます(mdn web docs 参照)。

イベントオブジェクトの例

以下は、キーボードイベント(KeyboardEvent)が発生した際のオブジェクトとなります。
イベントの種類によって、オブジェクトの内容は異なります。

vue_4-3-11.png

イベント取得の基本構文

イベントオブジェクトは、次のように引数に $event と指定することで取得できます。

v-on:イベント名="メソッド名($event, その他の引数)"

取得したイベントオブジェクトは、JavaScript 側(アプリケーションインスタンス側)で、次のように使用できます。

function メソッド名(event, その他の引数) {
  console.log(event)
}

上記の event は引数名なので、e でも何でも構いません。

他の引数がない場合は ($event) の記述は省略できる

イベントオブジェクトを受け取るメソッドに他の引数がない場合、構文どおりに書くと、次のように記述することになります。

v-on:イベント名="メソッド名($event)"

ただし、次のように ($event) を省略しても、JavaScript 側のメソッドの第 1 引数でイベントオブジェクトを取得することができます。

v-on:イベント名="メソッド名"

具体的な例で確認する

具体的な例で見た方が分かりやすいので、コードを書いていきましょう。
chapter3-4.html というファイルを追加した上で、次のように記載してください。

<div id="app">
  <div>
    <input v-on:keydown="onKeydown($event)" />
    <p>{{ message }}</p>
  </div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const message = ref('')
      const onKeydown = (event) => {
        console.log(event)
        message.value = `${event.key}キーを押しました(keyCode: ${event.keyCode})`
      }
      return {
        message,
        onKeydown,
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter3-4.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>
      <input v-on:keydown="onKeydown($event)" />
      <p>{{ message }}</p>
    </div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const message = ref('')
        const onKeydown = (event) => {
          console.log(event)
          message.value = `${event.key}キーを押しました(keyCode: ${event.keyCode})`
        }
        return {
          message,
          onKeydown,
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

オブジェクトイベントの取得部分を中心にコードを確認してみましょう。

① イベントオブジェクトの取得部分

次のように onKeydown メソッドの引数で $event という形でイベントオブジェクトを取得しています。

<input v-on:keydown="onKeydown($event)" />

なお、メソッドの引数が 1 つになるため、次のように省略して記載することもできます。

<input v-on:keydown="onKeydown" />

② メソッドの引数でイベントオブジェクトを受け取る

メソッド側では、引数 event で、イベントオブジェクトを受け取っています。

const onKeydown = (event) => {
  console.log(event)
  message.value = `${event.key}キーを押しました(keyCode: ${event.keyCode})`
}

console.log(event) のところで、Console 画面にイベントオブジェクトを表示します。
次の message.value = ... のところで、画面に表示するメッセージを生成しています。 ここに出てくる event.keyevent.keyCode は何なのか、ということですが、これはキーボードイベントで取得できるプロパティになります。 具体的には下図の赤枠部分の 2 つです。

vue_4-3-12.png

③ ブラウザで確認する

それでは、ブラウザで開いて確認をしてみましょう。
開発者用画面の Console のところを開いておいてください。

vue_4-3-13.png

何でも良いのですが、インプットボックスにキーボードから「a」を入力してみましょう。
「aキーを押しました(keyCode: 65)」という表示と、Console 画面にイベントオブジェクトの表示が確認できます。

vue_4-3-14.png

イベントオブジェクトの ▶ をクリックしてオブジェクトを展開してみてください。
赤枠部分のプロパティ key は、入力した「a」キーの名前となります。
もう一つのプロパティ keyCode は、「a」キーのキーコードとなります。

vue_4-3-15.png

ご自身で、いろいろなキーから入力して挙動を確認してみていただければと思います。
例えば、PageDown(pg dn) キーを押すと、次のように表示されます。

vue_4-3-16.png

3-3. キーを特定してイベントを実行する

今までは、どのキーを押してもイベント処理が走っていましたが、ここでは特定のキーに反応するイベント処理について説明します。

特定のキーイベントを指定する構文

キーイベントで特定のキーを指定する場合は次のように指定します。

v-on:イベント名.キー名="メソッド名"

キー名は、前の「3-2. キー押下時のイベントを取得する」の例で確認した、イベントオブジェクトの key プロパティ($event.key)と一致します。

キー名はケバブケースで指定する

キーイベントで特定のキーを指定する場合の「キー名」は、ケバブケースに直して指定する必要があります。
例えは、PageDown(pg dn) キーの key は "PageDown" ですが、このまま指定しても認識されません。
指定するときは、次のようにケバブケースに引き直すことが必要となります。

<input v-on:keydown.page-down="メソッド名" />

なお、"Enter" のように大文字を含んでいる場合も、"enter" と読み替えることになります。

具体的な例で確認する

実際に、コードを書いてみましょう。
chapter3-5.html というファイルを追加した上で、次のように記載します。

<div id="app">
  <div>
    <input v-on:keydown.a="onAKeydown" />
  </div>
</div>

<script>
  Vue.createApp({
    setup() {
      const onAKeydown = () => alert('aキーを押しました')
      return {
        onAKeydown
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter3-5.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>
      <input v-on:keydown.a="onAKeydown" />
    </div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        const onAKeydown = () => alert('aキーを押しました')
        return {
          onAKeydown
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を確認していきます。

① v-on ディレクティブ指定部分

次のように v-on:keydown.a という形式で末尾に「a」キーの名前を指定しています。

<input v-on:keydown.a="onAKeydown" />

これだけでイベントに反応するキーを特定することができます。

② メソッドの指定

メソッドの指定は次のとおりで、イベントが発火したらダイアログが表示されます。

const onAKeydown = () => alert('aキーを押しました')

③ ブラウザで確認する

それでは、ブラウザで確認しましょう。

vue_4-3-17.png

「a」キーを押して上記のように表示されれば OK です。
「a」キー以外の場合は反応しないことも確認してみてください。

キー修飾子を使用する

上記の「キー名」の代わりに、Vue で用意されたキー修飾子(キーコードのエイリアス)を使用することもできます。
Vue で使用できるキー修飾子は次のとおりです。

No キー修飾子 備考
1 .enter enter キー
2 .tab tab キー
3 .delete delete キー、backspace キーの両方に反応する
4 .esc esc キー
5 .space space キー
6 .up ↑ キー
7 .down ↓ キー
8 .left ← キー
9 .right → キー

上記のキー修飾子のうち esc キー(.esc)を使用する場合は次のようになります。

<div id="app">
  <div>
    <input v-on:keydown.esc="onEscKeydown" />
  </div>
</div>
全てのコードを表示

動作確認をする場合は、chapter3-6.html というファイルを追加した上で、次のように記載します。

chapter3-6.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>
      <input v-on:keydown.esc="onEscKeydown" />
    </div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        const onEscKeydown = () => alert('escキーを押しました')
        return {
          onEscKeydown
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

システム修飾子を使用する

システム修飾子は「ctrl キー」+「c キー」のように、イベント発生の条件として複数のキーを指定する場合に使用します。
システム修飾子は、次のように、イベント名とキー名の間に記述します。対象となるイベントは「キーボードイベント」と「マウスイベント」となります。

v-on:イベント名.システム修飾子.キー名="メソッド名"

Vue に用意されているシステム修飾子は次のとおりです。

No システム修飾子 備考
1 .ctrl ctrl キー
2 .alt alt キー
3 .shift shift キー
4 .meta Windows の場合はウィンドウキー(⊞)、Mac の場合はコマンドキー(⌘)

例えば「ctrl キー」+「q キー」でイベント発火させる場合は次のように記述します。

<div id="app">
  <div>
    <input v-on:keydown.ctrl.q="onCtrlAndQKeydown" />
  </div>
</div>
全てのコードを表示

動作確認をする場合は、chapter3-6-2.html というファイルを追加した上で、次のように記載します。

chapter3-6.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>
      <input v-on:keydown.ctrl.q="onCtrlAndQKeydown" />
    </div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        const onCtrlAndQKeydown = () => alert('ctrl + q キーを押しました')
        return {
          onCtrlAndQKeydown
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

4. インプットイベント

次に インプットイベントについてです。

インプットイベントは、input タグなど、編集可能な HTML 要素の値が変更された際に発生するイベントとなります。

本チャプターでは、HTML 側で入力されたデータ内容をリアクティブに JavaScript で取得する例を見ながら学習をしていきます。

4-1. 入力データを取得する基本構文

インプットイベントで、入力内容を取得する場合は、おおよそ次の形になります。

v-on:input="メソッド名($event, その他の引数)"

setup 関数内(JavaScript 側)では次のようにして入力値を受け取ります。

function メソッド名(event, その他の引数) {
  console.log(event.target.value)
}

input タグなどで入力されたデータは、イベントオブジェクトのtarget.value プロパティに格納されますので、 event.target.value という形で入力データを取得することができます(event は引数名なので e としても構いません)。

4-2. 具体的な例で確認する

早速、具体的な例で見ていきます。 chapter3-7.html というファイルを追加した上で、次のようにコードを記載してください。

<div id="app">
  <div>
    <input v-on:input="onInput($event)" />
    <div>{{ inputText }}</div>
  </div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const inputText = ref('')
      const onInput = (e) => {
        console.log(e)
        inputText.value = e.target.value
      }
      return {
        inputText,
        onInput
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter3-7.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>
      <input v-on:input="onInput($event)" />
      <div>{{ inputText }}</div>
    </div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const inputText = ref('')
        const onInput = (e) => {
          console.log(e)
          inputText.value = e.target.value
        }
        return {
          inputText,
          onInput
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を確認していきます。

① v-on ディレクティブ指定部分

次のように v-on:input="onInput($event)" と指定して、入力が行われたらイベントを取得するようにしています。

<input v-on:input="onInput($event)" />

なお、引数は $event の 1 つだけなので次のように省略しても構いません。

<input v-on:input="onInput" />

② メソッド部分

メソッド onInput の指定は次のとおりです。

const onInput = (e) => {
  console.log(e)
  inputText.value = e.target.value
}

console.log(e) のところは、取得したイベントを Console に表示するためのものです。
inputText.value = e.target.value のところで、イベントオブジェクトから入力値を取得し、リアクティブ変数 inputText に代入をしています。

このリアクティブ変数 inputText の値は、テンプレートの以下の部分で画面に表示されるようにしています。

<div>{{ inputText }}</div>

③ ブラウザで確認する

ブラウザで開いて、開発者画面の Console を表示した状態で「abc」と入力してみましょう。
入力するたびに input ボックスの下側に、入力値がリアクティブに反映されるのが確認できると思います(赤字の「abc」の部分)。
また、右側の Console 画面には、入力のたびにイベントオブジェクト(InputEvent)が表示されています。

vue_4-3-18.png

上図の 3 つ目のイベントオブジェクトの をクリックして中を確認してみましょう。

次のように展開されたら、今度は target の横の をクリックしてください。
この target の中に、e.target.value で指定した value プロパティがあるはずです。

vue_4-3-19.png

target を展開すると、アルファベット順で結構な数のプロパティが表示されます。
ずっと下にスクロールして下図の (...) を見つけてクリックしてください。すると、更に続きが表示されます。

vue_4-3-20.png

スクロールしていくと、次の value: "abc" が見つかるはずです(もし見つからなくとも、本チャプターの学習に支障はありませんのでご安心ください)。

vue_4-3-21.png

結論として、この value: "abc"e.target.value という指定でイベントオブジェクトから取得し、画面に表示しているということになります。

以上、インプットイベントで入力値を取得して画面に表示するまでの流れを確認しました。
Console 画面でオブジェクトの内容を確認することはよくありますので、確認の仕方などを把握しておくようにしましょう。

5. フォームに関するイベント

次に、フォームイベントについて見ていきましょう。
冒頭で紹介した主なイベント一覧のうち、フォームイベントには以下のものがありました。

No イベント名 説明
1 select テキストを選択したとき
2 change 要素の値が変更され、フォーカスを失ったとき
3 submit フォーム送信(submit)ボタンが押下されたとき
4 focus フォーカスしたとき
5 blur フォーカスが外れたとき

ここでは、change,focus,blur の 3 つのイベントについて具体例を見ていきます。

5-1. change イベントの具体例

change イベントも要素の値の変更をキャッチする点では、input イベントに近しい役割があります。 ただし、change イベントが発生するには「要素の値の変更」という条件に加えて「フォーカスが外れた」という条件が必要になります。

つまり、文字を入力している間はイベントは発火せず、入力が完了してフォーカスが外れたときに始めてイベントが発火するということになります。

早速、具体的な例で見ていきます。 chapter3-8.html というファイルを追加した上で、次のようにコードを記載してください。

<div id="app">
  <div>
    <input v-on:change="onChange($event)" />
    <div>{{ inputText }}</div>
  </div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const inputText = ref('')
      const onChange = (e) => {
        console.log(e)
        inputText.value = e.target.value
      }
      return {
        inputText,
        onChange
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter3-8.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>
      <input v-on:change="onChange($event)" />
      <div>{{ inputText }}</div>
    </div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const inputText = ref('')
        const onChange = (e) => {
          console.log(e)
          inputText.value = e.target.value
        }
        return {
          inputText,
          onChange
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容は、先に見たインプットイベントの例(chapter3-7.html)とほぼ同じです。

① v-on ディレクティブ指定部分

次のように v-on:change と指定しています。

<input v-on:change="onChange($event)" />

② メソッド部分

メソッドの中身は chapter3-7.html と同じで、メソッド名を onChange と変えているだけです。

const onChange = (e) => {
  console.log(e)
  inputText.value = e.target.value
}

③ ブラウザで確認する

それでは、input イベントと change イベントの違いをブラウザで確認してみましょう。

ここでも、開発者画面の Console を表示した状態で「abc」と入力してみてください。
入力中はイベントは発生せず、何の変化も起きません。
なお、入力ボックスの周りが太枠になっているのは「フォーカスが当たっている」ためです。

vue_4-3-22.png

次に、フォーカスを外してみましょう。
入力ボックス以外の場所を、どこでもよいのでクリックしてみてください。
フォーカスが外れた瞬間に、次のように change イベントが実行されます。

vue_4-3-23.png

なお、右側のイベントオブジェクトの中身を確認すると、value: "abc" という値を確認することができます。
これがリアクティブに取得され画面に表示されているということになります。

vue_4-3-24.png

5-2. フォーカスに関するイベント(focus,blur)

次に、フォーカスが当たった場合(focus)と外れた場合(blur)のイベントについてコードを書いて確認していきます。

chapter3-9.html というファイルを追加した上で、次のようにコードを記載してください。

<div id="app">
  <div>
    <input v-on:focus="onFocus" v-on:blur="onBlur"/>
  </div>
</div>

<script>
  Vue.createApp({
    setup() {
      const onFocus = (e) => console.log('フォーカスされました')
      const onBlur = (e) => console.log('フォーカスが外れました')
      return {
        onFocus,
        onBlur
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter3-8.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div>
      <input v-on:focus="onFocus" v-on:blur="onBlur"/>
    </div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        const onFocus = (e) => console.log('フォーカスされました')
        const onBlur = (e) => console.log('フォーカスが外れました')
        return {
          onFocus,
          onBlur
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を見てみましょう。

① v-on ディレクティブ指定部分

v-on を使用して次のように focus イベントと blur イベントを登録しています。

<input v-on:focus="onFocus" v-on:blur="onBlur"/>

② メソッド部分

フォーカスがされた場合とフォーカスが外れた場合のそれぞれにおいて Console 画面にメッセージを出力するようにしています。

const onFocus = (e) => console.log('フォーカスされました')
const onBlur = (e) => console.log('フォーカスが外れました')

③ ブラウザで確認する

それでは、ブラウザで確認してみましょう。
右クリックで「検証」を選択し、開発者画面の Console を表示します。

入力ボックス内をクリックすると、focus イベントが発火して Console に「フォーカスされました」と表示されます。

vue_4-3-25.png

次に、入力ボックスの外側をクリックしてみます。
すると、blur イベントが発火して「フォーカスが外れました」と表示されます。

vue_4-3-26.png

単純ですが、これらのイベントも実際によく使用されるものですので、覚えておくようにしましょう。

6. ページに関するイベント

最後のイベントとして、ページのスクロールイベントをキャッチする scroll イベントについてみていきます。

scroll イベントの具体例

早速、コードを書いていきましょう。 chapter3-10.html というファイルを追加した上で、次のようにコードを記載してください。

<div id="app">
  <div v-on:scroll="onScroll" style="height: 150px; overflow: auto;">
    <div v-for="n in 100" v-bind:key="n">こんにちは</div>
  </div>
</div>

<script>
  Vue.createApp({
    setup() {
      const onScroll = (e) => {
        console.log(e.target.scrollTop)
      }
      return {
        onScroll
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter3-8.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-on:scroll="onScroll" style="height: 150px; overflow: auto;">
      <div v-for="n in 100" v-bind:key="n">こんにちは</div>
    </div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        const onScroll = (e) => {
          console.log(e.target.scrollTop)
        }
        return {
          onScroll
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容について簡単に説明します。

① v-on ディレクティブ指定部分

次のように v-on:scroll="onScroll" と指定して、scroll イベントをキャッチしたら onScroll メソッドを呼び出すようにしています。

<div v-on:scroll="onScroll" style="height: 150px; overflow: auto;">

なお、style="height: 150px; overflow: auto;" の部分は、要素の高さを 150 px に固定して、強制的にスクロールをさせるための記述です。

② v-for 使用部分

次のところで v-for を使用して「こんにちは」というテキストを 100 回表示するようにしています。

<div v-for="n in 100" v-bind:key="n">こんにちは</div>

v-for="n in 100" という記述で、n を 1 から 100 まで繰り返すことになります。
v-for については Chapter 5 で学習しますので、ここでは何となくの理解で大丈夫です。

③ メソッド部分

イベントオブジェクトの target.scrollTop プロパティから、スクロールの位置(上側)を取得することができます。
スクロールされていない状態は 0 で、スクロールすると値が増えていきます。

console.log(e.target.scrollTop)

④ ブラウザで確認する

それでは、ブラウザで動作確認をしてみましょう。
ブラウザを開いて、開発者画面の Console を表示してください。

vue_4-3-27.png

「こんにちは」のエリアをスクロールさせてみましょう。
スクロールさせるたびに、イベントオブジェクトの target.scrollTop が取得できることが確認できると思います。

vue_4-3-28.png

7. イベント修飾子

一通り主なイベント処理についてみてきました。
ここでイベント実行時の挙動を制御する「イベント修飾子」について説明をします。

7-1. JavaScript の標準メソッドでイベント制御する例

例えば、HTML の <a> タグを使用すると、href 属性で指定した url に画面遷移するようになっています。

<a href="https://www.google.com/">Google 検索ページ</a>

これを「遷移させない」ようにするには、一般的には JavaScript の preventDefault() メソッドを使用することになります。

<div id="app">
  <a v-on:click="onClick($event)" href="https://www.google.com/">Google 検索ページ</a>
</div>

<script>
  Vue.createApp({
    setup() {
      function onClick(event) {
        event.preventDefault()
        console.log('クリックしました')
      }
      return {
        onClick
      }
    }
  }).mount('#app')
</script>
全てのコードを表示

動作確認をする場合は chapter3-11.html というファイルを作成して、次のコードを記載してください。

chapter3-8.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <a v-on:click="onClick($event)" href="https://www.google.com/">Google 検索ページ</a>
  </div>

  <script>
    Vue.createApp({
      setup() {
        function onClick(event) {
          event.preventDefault()
          console.log('クリックしました')
        }
        return {
          onClick
        }
      }
    }).mount('#app')
  </script
</body>
</html>

preventDefault() メソッドは、デフォルトのイベント処理をキャンセルして実行しないようにするメソッドです(mdn web docs - Event.preventDefault() 参照)。
上のコードのようにメソッド内で event.preventDefault() と指定することにより、<a> タグの「ページ遷移」というイベントをキャンセルすることができます。

7-2. Vue のイベント修飾子を使用してイベント制御する例

以上のように、JavaScript に用意された標準のメソッドを使用してイベントの制御を行うこともできますが、Vue では、これらの処理を簡潔に記載できるよう「イベント修飾子」というものが用意されています。

Vue のイベント修飾子には次のようなものがあります。

No イベント修飾子 説明
1 .stop event.stopPropagation() と同じ。現在のイベントのさらなる伝播を阻止する。
2 .prevent event.preventDefault() と同じ。デフォルトのイベント処理をキャンセルする。
3 .capture 子要素のイベントより先に親要素のイベントを実行する。
4 .self イベントターゲットがその要素自身のときだけ呼び出される(子要素がターゲットの場合は呼び出されない)。
5 .once イベントが 1 回のみ発火する。
6 .passive event.preventDefault() を呼び出さないようにする。

ここでは、.prevent 修飾子を使用した例で動作確認を行ってみましょう。 chapter3-12.html というファイルを追加した上で、次のようにコードを記載してください。

<div id="app">
  <a v-on:click.prevent="onClick" href="https://www.google.com/">Google 検索ページ</a>
</div>

<script>
  Vue.createApp({
    setup() {
      function onClick(event) {
        console.log('クリックしました')
      }
      return {
        onClick
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter3-8.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <a v-on:click.prevent="onClick" href="https://www.google.com/">Google 検索ページ</a>
  </div>

  <script>
    Vue.createApp({
      setup() {
        function onClick(event) {
          console.log('クリックしました')
        }
        return {
          onClick
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

① イベント修飾子指定部分

次の v-on:click.prevent="onClick" のところでイベント修飾子 .prevent を使用しています。 これにより、クリックすると onClick メソッドは実行されるもののページ遷移はしないということが期待されます。

<a v-on:click.prevent="onClick" href="https://www.google.com/">Google 検索ページ</a>

② ブラウザで確認する

それでは、ブラウザで動作確認をしてみましょう。
ブラウザを開いて、開発者画面の Console を表示した状態で「Google 検索ページ」をクリックしてください。

vue_4-3-29.png

「Google 検索ページ」をクリックしてもページ遷移は行われませんが、onClick メソッドの実行結果として Console に「クリックしました」が表示されるはずです。

他の修飾子の例はここでは割愛しますが、必要に応じてご自身でコードを書いて実行してみてください。

8. v-on の省略記法

v-on も v-bind と同様に使用頻度が高いため、以下のような省略記法が用意されています。

8-1. 省略記法の構文

以下のように、v-on: の部分は、単に @ という記載に省略できます。

<!-- 完全な構文 -->
<a v-on:click="doSomething"> ... </a>

<!-- 省略記法 -->
<a @click="doSomething"> ... </a>

<!-- 動的引数の省略記法 -->
<a @[event]="doSomething"> ... </a>

v-on の動的引数ついては次項で説明します。

8-2. 省略記法の具体例

具体的な例を 1 つ見てみましょう。
本チャプターのクリックイベントの例(chapter3-1.html)では以下のように v-on を指定していました。

<button v-on:click="onClick">クリック</button>

これを省略記法を使用して記述すると、次のようになります。

<button @click="onClick">クリック</button>

省略記法を使用するか否かは、チームの指針にもよるところですので、状況に合わせて使用するようにしましょう。

9. 動的引数を使用した v-on

最後に、v-on ディレクティブに動的引数を使用する場合について確認しておきます。

9-1. 動的引数の構文

v-on ディレクティブのイベント名に動的引数を使用する場合は、次のように記述します。

v-on:[イベントの変数名]="メソッド名"

イベントの変数名 の部分には「単一式」のみ指定できます。

9-2. 動的引数の具体例

実際に v-on に動的引数を使用してコードを書いてみましょう。
chapter3-13.html というファイルを追加した上で、次のように記載します。

<div id="app">
  <button v-on:[eventname]="onClick">クリック</button><br />
</div>

<script>
  Vue.createApp({
    setup() {
      const onClick = () => alert('クリックしました!')
      return {
        onClick,
        eventname: 'click'
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter3-13.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <button v-on:[eventname]="onClick">クリック</button><br />
  </div>

  <script>
    Vue.createApp({
      setup() {
        const onClick = () => alert('クリックしました!')
        return {
          onClick,
          eventname: 'click'
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を簡単に見てみましょう。

① 動的引数の使用部分

次のところが動的引数の使用部分です。

<button v-on:[eventname]="onClick">クリック</button><br />

eventname は全て小文字で記載していることに注意してください。
このレッスンで用いている CDN のように、HTML ファイルに直接記述する DOM 内テンプレートを使用する場合、イベント名に大文字を使用してもブラウザー側で小文字に変換されてしまう事象があるためです(詳しくは 動的引数の構文上の制約 参照)。

② setup 側の指定

テンプレート側で受け取る変数として、eventname'click' と指定しています。

return {
  onClick,
  eventname: 'click'
}

③ ブラウザで表示する

それでは、ブラウザで表示して確認してみましょう。
「クリック」ボタンを押して、次のようにダイアログが表示されれば、無事に動的引数を渡すことができたことになります。

vue_4-3-30.png

本チャプターでは v-on ディレクティブについて学習しました。

Lesson 4 Chapter 4
条件分岐(v-if, v-show)

このチャプターでは、Vue の条件分岐について学習していきます。
ここで取り扱うディレクティブは次のとおりです。

No ディレクティブ 説明
1 v-if 条件分岐を行う。
2 v-else-if 条件分岐を行う(v-if とともに使用する)。
3 v-else 条件分岐を行う(v-if とともに使用する)。
4 v-show 要素の表示・非表示を制御する。

以下、順に説明をしていきます。

1. 条件分岐(v-if)

プログラミング言語には、必ず条件分岐の構文があります。
条件が「真(true)」であれば処理を実行し、「偽(false)」であれば処理を実行しないというロジックになります。

例えば、JavaScript の条件分岐(if 文)は次のようになります(言うまでもないですが)。

function sample(val) {
  if (val === 1) {
    console.log('値は 1 です')
  }
}

sample(1)  // 値は 1 です
sample(2)  // (出力なし)

1-1. 基本構文

Vue では、v-if ディレクティブを使用して次のような条件分岐を行うことができます。

<div v-if="条件"> 条件が true の場合に表示 </div>

指定した条件が満たされた場合のみ要素が表示されます。
条件が false であれば、要素自体が表示されません。

1-2. v-if の具体例

具体例で確認をしてみましょう。
chapter4-1.html というファイルを追加した上で、次のように記載します。

<div id="app">
  <div v-if="val === 1">値は 1 です</div>
  <div v-if="val === 2">値は 2 です</div>
</div>

<script>
  Vue.createApp({
    setup() {
      return {
        val: 2
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter4-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-if="val === 1">値は 1 です</div>
    <div v-if="val === 2">値は 2 です</div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        return {
          val: 2
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を簡単に見てみましょう。

① 条件分岐(v-if)の使用部分

v-if は次のように指定しています。

<div v-if="val === 1">値は 1 です</div>
<div v-if="val === 2">値は 2 です</div>

今回の例では val2 を返しますので、1 行目は表示されず、2 行目の「値は 2 です」が表示されることになります。

② ブラウザで確認する

それでは、ブラウザで表示して確認してみましょう。

vue_4-4-1.png

画面には、想定どおりに「値は 2 です」が表示されています。
開発者用画面の Elements タブから HTML を確認すると、1 行目の要素は <div> タグも含めてレンダリングされていません(<!--v-if--> というコメントだけ残っています)。

2. 条件分岐(v-else-if,v-else)

次に、v-else-ifv-else について確認していきましょう。
この 2 つのディレクティブは、必ず v-if とともに使用します。

2-1. 基本構文

構文を確認してみましょう。
v-else-ifv-else を使用することで次のような条件分岐を行うことができます。
(基本的には、JavaScript の「if - else if - else」の構文と同様の挙動です。)

<div v-if="条件1"> 条件1が true の場合に表示 </div>
<div v-else-if="条件2"> 条件1が false かつ 条件2が true の場合に表示 </div>
<div v-else> 条件1、条件2がともに false の場合に表示 </div>

条件1 が true であれば、v-if の行が表示されます。
条件1 が false で、かつ 条件2 が true であれば、v-else-if の行が表示されます。
条件1条件2 ともに false であれば、v-else の行が表示されます。

2-2. v-else-if,v-else の具体例

具体例で確認をしてみましょう。
chapter4-2.html というファイルを追加した上で、次のように記載します。

<div id="app">
  <input type="number" v-on:input="inputNum" />
  <div v-if="val === 1">値は 1 です</div>
  <div v-else-if="val === 2">値は 2 です</div>
  <div v-else>値は 1 でも 2 でもありません</div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const val = ref(0)
      const inputNum = (e) => val.value = Number(e.target.value)
      return {
        val,
        inputNum
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter4-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <input type="number" v-on:input="inputNum" />
    <div v-if="val === 1">値は 1 です</div>
    <div v-else-if="val === 2">値は 2 です</div>
    <div v-else>値は 1 でも 2 でもありません</div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const val = ref(0)
        const inputNum = (e) => val.value = Number(e.target.value)
        return {
          val,
          inputNum
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を確認していきましょう。

① 条件分岐(v-if,v-else-if,v-else)の使用部分

v-if、v-else-if、v-else は次のように指定しています。

<div v-if="val === 1">値は 1 です</div>
<div v-else-if="val === 2">値は 2 です</div>
<div v-else>値は 1 でも 2 でもありません</div>

val に渡される値によって、1 行目から 3 行目のいずれかの行が表示されることになります。

② val の指定部分

以下で、input ボックスに値が入力されるたびに inputNum メソッドが実行されるようになっています(Chapter 3 で学習した内容です)。

<input type="number" v-on:input="inputNum" />

setup 関数内で inputNum メソッドは、引数でイベントオブジェクト e を受け取り、入力値(e.target.value)を変数 val に代入しています。

const val = ref(0)
const inputNum = (e) => val.value = Number(e.target.value)
return {
  val,
  inputNum
}

val は、テンプレート側の条件分岐に使用されることになります。

③ ブラウザで確認する

ブラウザで開いて、開発者用画面の Elements を選択して HTML も表示しておきましょう。
val の初期値は 0 なので、v-else で指定した 3 行目の「値は 1 でも 2 でもありません」が表示されています。

vue_4-4-2.png

次に、入力ボックスに 1 と入力してみましょう。
すると v-if で指定した条件に合致することになるので、画面の表示は「値は 1 です」に変わり、HTML も次のように入れ替わりました。

vue_4-4-3.png

最後に、入力ボックスに 2 と入力してみましょう。
v-else-if で指定した条件に合致するため、画面の表示は「値は 2 です」に変わり、HTML も入れ替わります。

vue_4-4-4.png

以上のように「v-if、v-else-if、v-else」を使用すると、条件に合致する行のみがレンダリングされるようになります。

3. 要素の表示・非表示の制御(v-show)

続いて、要素の表示・非表示を制御する v-show ディレクティブについて見ていきます。

3-1. 基本構文

v-show ディレクティブの使用方法は v-if ディレクティブとほぼ同じです。
構文は次のようになります。

<div v-show="条件"> 条件が true の場合に表示 </div>

条件が満たされた場合はブラウザ画面に要素が表示されます。
なお、条件が false であれば、ブラウザ画面に要素は表示されませんが、HTML 自体はレンダリングされます(※後ほどの具体例で確認します)。

3-2. v-show の具体例

v-show について具体例で確認をしていきしょう。
chapter4-3.html というファイルを追加した上で、次のように記載します。
(※ v-if の具体例で作成した chapter4-1.htmlv-ifv-show に変更しただけです)

<div id="app">
  <div v-show="val === 1">値は 1 です</div>
  <div v-show="val === 2">値は 2 です</div>
</div>

<script>
  Vue.createApp({
    setup() {
      return {
        val: 2
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter4-3.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-show="val === 1">値は 1 です</div>
    <div v-show="val === 2">値は 2 です</div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        return {
          val: 2
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を簡単に見てみましょう。

① 要素の表示・非表示(v-show)の使用部分

v-show は次のように指定しています。

<div v-show="val === 1">値は 1 です</div>
<div v-show="val === 2">値は 2 です</div>

② ブラウザで確認する

それでは、ブラウザで表示して確認してみましょう。
開発者用画面の Elements タブから HTML も表示させてください。

vue_4-4-5.png

v-show ディレクティブも v-if ディレクティブの時と同じく、左側のブラウザ画面には 2 行目の「値は 2 です」が表示され、1 行目は表示されていません。
しかし、右側の HTML を見ると <div style="display: none;">値は 1 です</div> と 1 行目も レンダリングされています。 つまり、レンダリングはするものの style="display: none; というように CSS を使用することで画面を表示させない処理をしているわけです(※)。

v-if と v-show のどちらを使うべきか

v-show は、初期条件が false の場合でも必ずレンダリングをするため、初期の処理コストが高いという面があります。
一方、v-if は表示・非表示を切り替えるたびに再レンダリングを行うため、切替え時のコストは高くなります。

どちらを使用するかはチームの方針にもよると思いますが、レンダリングコストを抑えるという観点からは、頻繁に切替えが必要な要素には v-show を選び、切替えがあまり生じない要素には v-if を選ぶのが良いということになります。

Lesson 4 Chapter 5
繰返し処理(v-for)

このチャプターでは、v-for ディレクティブを使用した繰り返し処理について学んでいきます。

1. v-for ディレクティブの概要

v-for ディレクティブを使用すると、配列やオブジェクトの要素を 1 つずつ取り出し、並べて表示させることができます。

v-for のイメージを掴むため、まず、最小限の構成で v-for について確認してみましょう。

1-1. v-for ディレクティブの単純な構文

最低限の構文は次のようになります。

v-for="変数名 in 配列またはオブジェクト名"

このディレクティブを設定した HTML 要素は、配列(またはオブジェクト)の要素数だけ、繰り返し描画されます。

1-2. v-for を使用した最小限の例

以下は、配列 items に対して v-for ディレクティブを使用した例です。
chapter5-1.html というファイルを作成してコードを記載しましょう。

<div id="app">
  <div v-for="item in items">{{ item }}</div>
</div>

<script>
  Vue.createApp({
    setup() {
      const items = ['りんご', 'みかん', 'パイナップル']
      return {
        items
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter5-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-for="item in items">{{ item }}</div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        const items = ['りんご', 'みかん', 'パイナップル']
        return {
          items
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を簡単に見てみましょう。

① v-for ディレクティブの使用部分

以下のところで v-for ディレクティブを使用しています。

<div v-for="item in items">{{ item }}</div>

items は、setup 関数側(JavaScript 側)から渡された配列名です。
この配列の要素数は 3 つなので、<div> 要素は 3 回分繰り返し描画されます。
items から取り出した要素は 1 つずつ item という変数で取得されます(変数名は何でも構いません)。
この取り出した要素を、順に {{ item }} で表示するという処理がされることになります。

② ブラウザで表示する

ブラウザで表示すると、次のように配列の内容が表示されます。
開発者用画面の Elements で HTML を見ると、v-for で指定した要素が、配列の長さ分(3 回分)レンダリングされていることが確認できます。

vue_4-5-1.png

以上、簡単な例で v-for を確認しました。
実際は、以上の指定だけでは不十分なため、以下、もう少し踏み込んで見ていきます。

2. key 属性

v-for ディレクティブを使用する際は「key 属性」というものを付けることが推奨されています。基本的には key 属性を付けることが必須と考えて差し支えありません(※)。

key 属性の役割

Vue が v-for のリストを更新したり並べ替えをしたりする際は「key 属性を識別情報」として再レンダリングが行われます。 key 属性を付けていない場合はこの再レンダリングが正しく実行されない場合が生じえます。

2-1. key 属性を付けた v-for ディレクティブの構文

構文は次のようになります。
key 属性は各要素ごとに一意(ユニーク)でなければなりません。

v-for="変数名 in 配列またはオブジェクト名" v-bind:key="ユニークなキー"

v-bind:key のところは、省略記法を用いて :key とする場合が多いです。

2-2. key 属性を追加する

先ほど作成した chapter5-1.html に key 属性を追加してみましょう。

① 修正前

修正するのは、以下のテンプレートの部分です。

<div id="app">
  <div v-for="item in items">{{ item }}</div>
</div>

② 修正後

次のように v-bind:key="item" を追加します。

<div id="app">
  <div v-for="item in items" v-bind:key="item">{{ item }}</div>
</div>
全てのコードを表示
chapter5-1-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-for="item in items" v-bind:key="item">{{ item }}</div>
  </div>

  <script>
    Vue.createApp({
      setup() {
        const items = ['りんご', 'みかん', 'パイナップル']
        return {
          items
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

key 属性には「数値や文字列などのプリミティブな値」かつ「ユニークな値」を割り当てるようにします。
この例で key に割り当てられるのは、'りんご''みかん''パイナップル' の 3 つですので、key はプリミティブな値(文字列)かつユニークという条件を見たします。

③ ブラウザで表示する

念のため、ブラウザで表示してみましょう。
修正前と変わりなく表示されれば OK です。

vue_4-5-2.png

3. 配列で v-for を使用する

今までは、簡素な例を見てきましたが、1 つずつ詳しく見ていきます。
まずは、配列で v-for を使用する場合です。

3-1. 配列で v-for を使用する構文

① 基本構文(インデックスを取得しない場合)

インデックスを取得しない場合はこれまでと変わりありません。

v-for="変数名 in 配列名" :key="ユニークなキー"

② インデックスを取得する場合

配列のインデックスを取得するには、次のように 2 つ目の引数を設定します。

v-for="(変数名, index) in 配列名" :key="ユニークなキー"

例えば、先ほどの chapter5-1.html に index を追加すると次のようになります。

<div id="app">
  <div v-for="(item, index) in items" :key="item"> {{ index }}: {{ item }}</div>
</div>

なお、index は任意の名称で構わないので i と省略しても差し支えありません。

3-2. 配列のインデックスを取得する具体例

それでは、v-for でインデックスを取得する具体例を見ていきましょう。
chapter5-2.html というファイルを作成して次のようにコードを記載します。

<div id="app">
  <div v-for="(user, index) in users" :key="user.name">
    {{ index }}: {{ user.name }} {{ user.age }}歳
  </div>
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const users = ref([
        { name: '山田太郎', age: 26 },
        { name: '鈴木花子', age: 38 },
        { name: '吉田義男', age: 31 },
      ])
      return {
        users
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter5-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-for="(user, index) in users" :key="user.name">
      {{ index }}: {{ user.name }} {{ user.age }}歳
    </div>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const users = ref([
          { name: '山田太郎', age: 26 },
          { name: '鈴木花子', age: 38 },
          { name: '吉田義男', age: 31 },
        ])
        return {
          users
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を簡単に見ていきます。

① 配列の指定

現実的な例となるように、今回は、オブジェクトを要素とする配列にしています。
なお、ref 関数を使用してリアクティブ化もしています(ここでは値の変更は行いませんが)。

const users = ref([
  { name: '山田太郎', age: 26 },
  { name: '鈴木花子', age: 38 },
  { name: '吉田義男', age: 31 },
])

② v-for ディレクティブの指定部分

(user, index) in usersindex で配列のインデックス番号を取得しています。

<div v-for="(user, index) in users" :key="user.name">
  {{ index }}: {{ user.name }} {{ user.age }}歳
</div>

:key には、ユニーク値として user.name を当てています(※)。

:key に配列のインデックスを使用することの是非

技術ブログなどで、:key に配列のインデックスを当てる記事などもありますが、配列の要素が削除されたり追加されたりしてインデックス番号が変更された場合は、識別情報としての役割を失い、バグを引き起こす原因となります。
実装の内容にもよりますが、:key には配列のインデックスを使用しない方が安全です。

③ ブラウザで表示する

ブラウザで表示してみましょう。

vue_4-5-3.png

以上のように、012 とインデックス番号が表示されていれば OK です。

4. オブジェクトで v-for を使用する

続いて、オブジェクトで v-for を使用する場合です。
v-for は、オブジェクトについてもプロパティ単位で反復取得することができます。

4-1. オブジェクトで v-for を使用する構文

① 基本構文(プロパティの値のみを取得)

基本構文はこれまでと変わりありません。

v-for="value in オブジェクト名" :key="ユニークなキー"

value はプロパティの値を取得するための任意の名称です。

② キー名を取得する場合

プロパティのキー名を取得するには、次のように 2 つ目の引数を設定します。

v-for="(value, name) in オブジェクト名" :key="ユニークなキー"

こちらも、name の変数名は何でもよく、任意の名称で構いません。

③ インデックスを取得する場合

プロパティのインデックスを取得するには、次のように 3 つ目の引数を設定します。

v-for="(value, name, index) in オブジェクト名" :key="ユニークなキー"

言うまでもないですが、index は任意の名称で構いません。

オブジェクトの処理順

v-for でオブジェクトを繰り返し処理するときの順序は JabaScript の Object.keys() メソッドで得られる順番に従います。
この順序は JavaScript エンジンにより差異があるため、順序の一貫性が保証されているわけではありません(公式 - オブジェクトの v-for 参照)。

4-2. オブジェクトを v-for で繰り返し処理する具体例

それでは、コードを書いて確認していきましょう。
chapter5-3.html というファイルを作成して次のように記載してください。

<div id="app">
  <div v-for="(value, name, index) in person" :key="name">
    {{ index }} {{ name }}: {{ value }}
  </div>
</div>

<script>
  const { createApp, reactive } = Vue

  createApp({
    setup() {
      const person = reactive(
        {
          name: '山田太郎',
          age: 26,
          prefecture: '東京都'
        }
      )
      return {
        person
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter5-3.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-for="(value, name, index) in person" :key="name">
      {{ index }} {{ name }}: {{ value }}
    </div>
  </div>

  <script>
    const { createApp, reactive } = Vue

    createApp({
      setup() {
        const person = reactive(
          {
            name: '山田太郎',
            age: 26,
            prefecture: '東京都'
          }
        )
        return {
          person
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を簡単に説明します。

① オブジェクトの指定

次のように 3 つのプロパティをもつオブジェクトを指定しています
なお、リアクティブ化に reactive 関数を使用していますが、ref 関数でも差し支えありません。

const person = reactive(
  {
    name: '山田太郎',
    age: 26,
    prefecture: '東京都'
  }
)

② v-for ディレクティブの指定部分

(value, name, index) in personvalue でプロパティの値を、 name でプロパティのキー名を、 index でプロパティのインデックス番号を取得しています。

<div v-for="(value, name, index) in person" :key="name">
  {{ index }} {{ name }}: {{ value }}
</div>

:key に指定するのは、ユニークであることが保証されているプロパティのキー名(name)が妥当でしょう。
インデックス番号は JabaScript の Object.keys() メソッドで取得される順序に従います。

③ ブラウザで表示する

それでは、ブラウザで表示してみましょう。

vue_4-5-4.png

3 つのプロパティが取得できました。
指定したとおり、左から順に「インデックス番号」「プロパティのキー名」「プロパティの値」の順で表示されています。

5. 数値で v-for を使用する

v-for で繰り返し処理する回数を数値で指定することもできます。

5-1. 数値で v-for の繰り返し数を指定する構文

以下のように指定することで、数値 に指定した回数分、要素をレンダリングします。

v-for="n in 数値" :key="n"

n には、1 から 数値 までの値が順に代入されます。

5-2. 数値で v-for を使用する具体例

それでは、コードを書いて確認していきます。
chapter5-4.html というファイルを作成して次のように記載してください。

<div id="app">
  <div v-for="n in 10" :key="n">{{ n }} 回目</div>
</div>

<script>
  Vue.createApp().mount('#app')
</script>
全てのコードを表示
chapter5-3.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-for="n in 10" :key="n">{{ n }} 回目</div>
  </div>

  <script>
    Vue.createApp().mount('#app')
  </script>
</body>
</html>

ルートコンポーネントで指定する内容は無いため、Vue.createApp().mount('#app') というように Vue の機能を使用するためだけにインスタンスを生成しています。

① v-for ディレクティブの指定部分

以下の指定で、要素を 10 回レンダリングします。

<div v-for="n in 10" :key="n">{{ n }} 回目</div>

② ブラウザで表示する

以下のように表示されれば OK です。

vue_4-5-5.png

このチャプターの学習は以上です。

Lesson 4 Chapter 6
双方向データバインディング(v-model)

本チャプターでは、v-model ディレクティブについて学んでいきます。
この v-model を使用することで、簡単に「双方向データバインディング」を実現することができます。

双方向データバインディングとは

例えば、A 側で値が更新されたら B 側の値も更新され、逆に B 側で値が更新されたら A 側でも値が更新されるというように、双方向にデータが連動することを「双方向データバインディング」といいます。

Vue に置き直して説明しますと、テンプレート側(HTML 側)で値が更新されたらスクリプト側(JavaScript 側)でも値が更新され、逆に、スクリプト側で値が更新されたらテンプレート側でも値が更新されるような仕組みとなります。

1. v-model を使用しない双方向データバインディング

v-model を使用しなくとも、これまで学習してきた内容で双方向データバインディングを実現することができます。
以下、順を追って見ていきます。

1-1. v-on ディレクティブを使用した単方向データバインディング

まず、手始めに、v-on ディレクティブを使用して、テンプレート側(HTML 側)からスクリプト側(JavaScript 側)への単方向データバインディングを作成します。

chapter6-1.html というファイルを作成して次のように記載してください。

<div id="app">
  <input v-on:input="onInput" />
  <button v-on:click="resetMessage">リセット</button>
  <div>{{ message }}</div>
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const message = ref('Hello World!')
      const onInput = (event) => message.value = event.target.value
      const resetMessage = () => message.value = 'Hello!'
      return {
        message,
        onInput,
        resetMessage
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter6-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <input v-on:input="onInput" />
    <button v-on:click="resetMessage">リセット</button>
    <div>{{ message }}</div>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const message = ref('Hello World!')
        const onInput = (event) => message.value = event.target.value
        const resetMessage = () => message.value = 'Hello!'
        return {
          message,
          onInput,
          resetMessage
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

コードの内容を簡単に説明します。

① 入力ボックス更新時の処理

入力ボックス更新時の処理として、テンプレート側では、input イベント発生時に onInput メソッドを実行するようにしています。

<input v-on:input="onInput" />

スクリプト側の onInput メソッドでは、message 変数を event.target.value(入力テキスト)で更新します(下記)。

const onInput = (event) => message.value = event.target.value

以上により、テンプレート側の入力ボックスで更新された値が、スクリプト側(JavaScript 側)に反映されることになります(単方向データバインディング)。

② スクリプト側での値の更新

スクリプト側(JavaScript 側)で message 変数が更新された場合に、入力ボックスに値が反映されるかを確認するために、次の resetMessage メソッドを作成しています。

const resetMessage = () => message.value = 'Hello!'

このメソッドが実行されると、message 変数は 'Hello!' に更新されます。
しかし、この変更は、テンプレート側の入力ボックスには反映されないということになるはずです。

③ ブラウザで表示する

実際に操作した方が分かりやすいと思いますので、ブラウザで確認をしてみましょう。

vue_4-6-1.png

何でも良いので、文字を入力してみます。
次のように「abcde」と入力すると、その下側に {{ message }} の内容が描画されます。

vue_4-6-2.png

これで、テンプレート側の変更は、スクリプト側(JavaScript 側)にリアクティブに反映されることが確認できました。

次に「リセット」ボタンを押してみましょう。
このボタンを押すと resetMessage メソッドが実行され、スクリプト側(JavaScript 側)で message の値が 'Hello!' に書き換えられます。

vue_4-6-3.png

上記の結果のとおり、{{ message }} の部分は 'Hello!' に更新されましたが、入力ボックスは 'abcde' のまま変わりません。
次の節で、この 'abcde' も同時に 'Hello!' に更新されるようにコードに修正を加えていきます。

1-2. v-on,v-bind ディレクティブを使用した双方向データバインディング

引き続いて、以上で記載したコードを双方向データバインディングに修正していきます。
スクリプト側(JavaScript 側)で更新した値を入力ボックス(input 要素)に反映するために v-bind ディレクティブを使用します。

chapter6-1.html ファイルのテンプレート部分を次のように修正します。

① 修正前

修正するのは、次の input 要素の部分です。

<input v-on:input="onInput" />

② 修正後

次のように v-bind:value="message" を追加します。

<input v-on:input="onInput" v-bind:value="message" />
全てのコードを表示
chapter6-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <input v-on:input="onInput" v-bind:value="message" />
    <button v-on:click="resetMessage">リセット</button>
    <div>{{ message }}</div>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const message = ref('Hello World!')
        const onInput = (event) => message.value = event.target.value
        const resetMessage = () => message.value = 'Hello!'
        return {
          message,
          onInput,
          resetMessage
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

以上のように v-bind を使用することによって、JavaScript 内の message の変更がテンプレート側に反映されるはずです。

③ ブラウザで表示する

それでは修正したコードについてブラウザで確認をしてみましょう。
ブラウザをリロードするか開き直してください。

vue_4-6-4.png

まず、修正前と異なるのは、入力ボックスに message の初期値「Hello World!」が表示されていることです。

次のように「Hello World!」「abcde」と入力すると、その下側にも同じ内容が描画されます(これは前回と変わりません)。

vue_4-6-5.png

次に「リセット」ボタンを押してみましょう。

vue_4-6-6.png

以上のように、スクリプト側(JavaScript 側)で更新された message の内容が入力ボックスにも反映されました。
これで、HTML 側(テンプレート側)とスクリプト側(JavaScript 側)の双方向データバインディングが成立したことになります。

2. v-model ディレクティブの構文など

前置きが長くなりましたが、ここからが本題です。
まずは、v-model の構文から見ていきましょう。

2-1. v-model ディレクティブの構文

v-model は次のように使用します。

v-model="リアクティブ変数"

これだけで、双方向データバインディングが実現します。

2-2. v-model の構成

v-model の最低限の構成を書くと次のとおりです。
メソッドなども不要で、シンプルに記載することができます。

① テンプレート側(HTML 側)

<input v-model="message" />

② スクリプト側(JavaScript 側)

const message = ref('')
① と ② を合わせたコードを表示
<div id="app">
  <input v-model="message" />
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const message = ref('')
      return {
        message,
      }
    }
  }).mount('#app')
</script>

3. v-model ディレクティブを使用した具体例

3-1. v-on,v-bind を v-model に置き換える

まず、chapter6-1.html で作成した双方向データバインディングのコードを v-model を使用して書き換えてみます。
新たに chapter6-2.html ファイルを作成した上で次のコードを記載してください。

<div id="app">
  <input v-model="message" />
  <button v-on:click="resetMessage">リセット</button>
  <div>{{ message }}</div>
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const message = ref('Hello World!')
      const resetMessage = () => message.value = 'Hello!'
      return {
        message,
        resetMessage
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter6-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <input v-model="message" />
    <button v-on:click="resetMessage">リセット</button>
    <div>{{ message }}</div>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const message = ref('Hello World!')
        const resetMessage = () => message.value = 'Hello!'
        return {
          message,
          resetMessage
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

v-model に関連する部分を見ていきましょう。

① テンプレート側の記載

v-model の指定箇所は次のところです(その他は、動作確認用のコードです)。

<input v-model="message" />

② スクリプト側の記載

スクリプト側で v-model と連動するのは次のところです(その他は動作確認用です)。

const message = ref('Hello World!')

③ ブラウザで表示する

早速、ブラウザで確認をしてみましょう。
適当に文字を入力すると、次のように {{ message }} の箇所がリアクティブに更新されることが確認できると思います。

vue_4-6-7.png

次にリセットボタンを押すと、スクリプト側(JavaScript 側)におけるデータ変更が、そのまま、テンプレート側の入力ボックスに反映されることが確認できます。

vue_4-6-8.png

以上、v-model を使用して双方向データバインディングを実現することができました。

3-2. 入力ボックスを 2 つ使用して v-model の動作確認をする例

もう少し、直感的に v-model の双方向データバインディングを確認できる例を見てみます。
新たに chapter6-3.html ファイルを作成した上で次のコードを記載してください。

<div id="app">
  入力 1: <input v-model="message" /><br />
  入力 2: <input v-model="message" />
</div>

<script>
  const { createApp, ref } = Vue

  createApp({
    setup() {
      const message = ref('')
      return {
        message
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter6-3.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    入力 1: <input v-model="message" /><br />
    入力 2: <input v-model="message" />
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const message = ref('')
        return {
          message
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

v-model に関連する部分を見ていきましょう。

① テンプレート側の記載

v-model の指定箇所は次のところです。
2 つの入力ボックスを並べてますが、どちらも同じ変数 message をバインドしています。

入力 1: <input v-model="message" /><br />
入力 2: <input v-model="message" />

② スクリプト側の記載

スクリプト側では単に、リアクティブ変数 message を 1 つ定義しているのみです。

const message = ref('')

このコードには、メソッドの定義は 1 つもありません。

③ ブラウザで表示する

ブラウザで確認をしてみましょう。次のように表示されるはずです。

vue_4-6-9.png

まず、「入力 1」の入力ボックスに、何か文字を打ってみましょう。
どちらの入力ボックスも message と双方向データバインディングしているので、次のように「入力 2」が連動して更新されます。

vue_4-6-10.png

上記は、「入力 1 の更新」 → 「message 変数の更新」 → 「入力 2 の更新」という順で、データの更新が伝達されていることになります。

次に、「入力 2」の入力ボックスに、適当に文字を打ってみましょう。back space などで文字を消しても OK です。
今度は「入力 1」の方が連動して更新されているのが分かると思います。

vue_4-6-11.png

4. チェックボックス・ラジオボタン・セレクトボックスと v-model

ここで、チェックボックス、ラジオボタン、セレクトボックスで v-model を使用する例を見ておきましょう。

4-1. チェックボックス

① 単一のチェックボックス

まず、単一のチェックボックスで v-model を使用する例です。
新たに chapter6-4.html ファイルを作成した上で次のコードを記載してください。

<div id="app">
  <input type="checkbox" v-model="toggle" />
  <div>{{ toggle }}</div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const toggle = ref(false)
      return { toggle }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter6-4.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <input type="checkbox" v-model="toggle" />
    <div>{{ toggle }}</div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const toggle = ref(false)
        return { toggle }
      }
    }).mount('#app')
  </script>
</body>
</html>

v-model を使用している箇所は次のところです。 チェックされていれば toggle の値は true に、チェックされていなければ false になります。

<input type="checkbox" v-model="toggle" />

ブラウザで確認をしてみましょう。
チェックをすると「true」が表示され、チェックを外すと「false」が表示されることを確認してみてください。

vue_4-6-12.png

② 複数のチェックボックス

次に、複数のチェックボックスで v-model を使用する例です。
新たに chapter6-5.html ファイルを作成した上で次のコードを記載しましょう。

<div id="app">
  <input type="checkbox" value="Jack" v-model="checkedNames" />Jack
  <input type="checkbox" value="John" v-model="checkedNames" />John
  <input type="checkbox" value="Mike" v-model="checkedNames" />Mike
  <div>Checked names: {{ checkedNames }}</div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const checkedNames = ref([])
      return { checkedNames }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter6-5.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <input type="checkbox" value="Jack" v-model="checkedNames" />Jack
    <input type="checkbox" value="John" v-model="checkedNames" />John
    <input type="checkbox" value="Mike" v-model="checkedNames" />Mike
    <div>Checked names: {{ checkedNames }}</div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const checkedNames = ref([])
        return { checkedNames }
      }
    }).mount('#app')
  </script>
</body>
</html>

v-model を使用している箇所は次のところです。
チェックされている項目の value の値が、配列 checkedNames に追加されます。

<input type="checkbox" value="Jack" v-model="checkedNames" />Jack
<input type="checkbox" value="John" v-model="checkedNames" />John
<input type="checkbox" value="Mike" v-model="checkedNames" />Mike

ブラウザで確認をしてみましょう。
チェックされた項目の value が checkedNames 配列に格納されるはずです。

vue_4-6-13.png

4-2. ラジオボタン

続いて、ラジオボタンで v-model を使用する例を見てみましょう。
新たに chapter6-6.html ファイルを作成した上で次のコードを記載してください。

<div id="app">
  <input type="radio" value="One" v-model="picked">One<br>
  <input type="radio" value="Two" v-model="picked">Two
  <div>Picked: {{ picked }}</div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const picked = ref('')
      return { picked }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter6-6.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <input type="radio" value="One" v-model="picked">One<br>
    <input type="radio" value="Two" v-model="picked">Two
    <div>Picked: {{ picked }}</div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const picked = ref('')
        return { picked }
      }
    }).mount('#app')
  </script>
</body>
</html>

v-model を使用している箇所は次のところです。 チェックされているラジオボタンの value が、変数 picked に格納されます。

<input type="radio" value="One" v-model="picked">One<br>
<input type="radio" value="Two" v-model="picked">Two

ブラウザで確認をしてみましょう。
正しく指定できていれば、選択されたラジオボタンの value が Picked: のところに表示されます。

vue_4-6-14.png

4-3. セレクトボックス

① 単一選択のセレクトボックス

単一選択のセレクトボックスで v-model を使用する例です。
新たに chapter6-7.html ファイルを作成した上で次のコードを記載してください。

<div id="app">
  <select v-model="selected">
    <option disabled value="" hidden>選択してください</option>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <div>選択: {{ selected }}</div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const selected = ref('')
      return { selected }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter6-7.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <select v-model="selected">
      <option disabled value="" hidden>選択してください</option>
      <option>A</option>
      <option>B</option>
      <option>C</option>
    </select>
    <div>選択: {{ selected }}</div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const selected = ref('')
        return { selected }
      }
    }).mount('#app')
  </script>
</body>
</html>

v-model を使用している箇所は次のところです。 選択した option の値が、変数 selected に格納されることになります。

<select v-model="selected">
  <option disabled value="" hidden>選択してください</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

ブラウザで確認をしてみましょう。
セレクトボックスを開いて、いずれかの option を選択します。

vue_4-6-15.png

次のように選択した値が「選択:」のところに表示されていれば OK です。

vue_4-6-16.png

② 複数選択のセレクトボックス

次に、複数の項目が選択できるセレクトボックスで v-model を使用する例です。
chapter6-8.html ファイルを作成した上で次のコードを記載してください。

<div id="app">
  <select v-model="selected" multiple>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <div>選択: {{ selected }}</div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const selected = ref([])
      return { selected }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter6-8.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <select v-model="selected" multiple>
      <option>A</option>
      <option>B</option>
      <option>C</option>
    </select>
    <div>選択: {{ selected }}</div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const selected = ref([])
        return { selected }
      }
    }).mount('#app')
  </script>
</body>
</html>

v-model を使用している箇所は次のところです。 選択した option の値が、配列 selected に格納されていくことになります。

<select v-model="selected" multiple>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

ブラウザで確認をしてみましょう。
コントロールボタンを押しながら、複数の option を選択してみてください。
コードに間違いがなければ、選択した option の値が、次のように配列で表示されます。

vue_4-6-17.png

以上のように、v-model を使用することで簡単にデータの双方向バインディングを実装することが可能となります。
v-model は Vue のプログラミングにおいて、頻繁に利用されますので、書き方や性質などをよく理解しておくようにしてください。

Lesson 4 Chapter 7
その他のディレクティブ

今まで見てきたディレクティブ以外にも、次のようなディレクティブが用意されています。
それぞれ、具体例を見ながら、使い方を確認しておきましょう。

No ディレクティブ 説明
1 v-slot slots(スロット)に名前を付ける。
2 v-pre 要素内のテキストをそのまま表示する。
3 v-cloak コンパイル完了まで要素に残る。
4 v-once 要素を 1 度だけ描画する。
5 v-memo 不要な再レンダリングを省略する。

1. v-slot ディレクティブ

v-slot ディレクティブを使用することで、親コンポーネントから子コンポーネントに、名前付きの slots(スロット)を渡すことができます。

とはいいましても、馴染みのない用語が出てきてイメージがしにくいと思いますので、先に次の 2 点について説明をします。

  • コンポーネントの親子関係
  • slots(スロット)

1-1. コンポーネントの親子関係

まず、コンポーネントの親子関係について説明をします(詳細は Lesson 6 で学習します)。

Vue プログラミングは「小さく、自己完結的で、再利用可能なコンポーネント」を組み合わせることでアプリケーションを構築していきます(下図は Vue公式 より転載)。

vue_4-7-0.png

上の図のように、1 つの画面は、様々なコンポーネントの組合せで構成されることになります。 大きな画面(親)は、いくつかの部品(子)を持ち、その部品は更に小さな部品(孫)で構成されるというような形式になります。

早速ですが、このコンポーネントの親子関係を、コード上でどのように定義するかについて確認していきたいと思います。

① 全体のコード
親コンポーネントと子コンポーネントを、簡素に定義すると次のとおりです。

<div id="app">
  <child-component></child-component>
</div>

<script>
  const { createApp, ref } = Vue

  // 子コンポーネント
  const ChildComponent = {
    template: `<div>{{ message }}</div>`,
    setup() {
      const message = ref('こんにちは')
      return {
        message
      }
    }
  }

  // 親コンポーネント
  createApp({
    components: {
      ChildComponent
    },
    setup() {
      //必要な処理を記述
      return {}
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter7-sample.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <child-component></child-component>
  </div>

  <script>
    const { createApp, ref } = Vue

    // 子コンポーネント
    const ChildComponent = {
      template: `<div>{{ message }}</div>`,
      setup() {
        const message = ref('こんにちは')
        return {
          message
        }
      }
    }

    // 親コンポーネント
    createApp({
      components: {
        ChildComponent
      },
      setup() {
        //必要な処理を記述
        return {}
      }
    }).mount('#app')
  </script>
</body>
</html>

② 子コンポーネント
子コンポーネントを定義しているのは、次の部分です。定義する内容は親コンポーネントの場合とほぼ変わりません。
テンプレートは template: のところに HTML を記述します。

// 子コンポーネント
const ChildComponent = {
  template: `<div>{{ message }}</div>`,
  setup() { 略 }
  }
}

③ 親コンポーネント
親コンポーネントのスクリプト側では、使用する子コンポーネントを components: のところに記述します。それ以外は普段と変わりません。

// 親コンポーネント
createApp({
  components: {
    ChildComponent
  },
  setup() { 略 }
})

親コンポーネントのテンプレート側では、使用する子コンポーネントを次のように指定します。 スクリプト側のコンポーネント名は ChildComponent ですが、テンプレートに記載するときは基本的にケバブケースで記載します。

<child-component></child-component>

上記の指定部分には、子コンポーネントで定義した次のテンプレートがレンダリングされることになります。

<div>{{ message }}</div>

以上、ざっとですが、親コンポーネントと子コンポーネントの定義方法について確認しました。
詳細は Lesson 6 で学習しますので、今のところは、おおよその書き方を把握しておいていただければ大丈夫です。

1-2. slots(スロット)

コンポーネントの親子関係について見てきましたが、ここで 1 つ問題となるのが「親子間のデータの受渡しをどうするか」ということです。

Vue には、下表のように、コンポーネント間のデータ受渡しについていくつかの機能が用意されています。

No 機能 簡単な説明
1 props 親コンポーネントから子コンポーネントに値を渡す。
2 emit 子コンポーネントから親コンポーネントにイベントを渡す。
3 slots 親コンポーネントから子コンポーネントに HTML テンプレート等を渡す。
4 状態管理機能 pinia や vuex などのライブラリでコンポーネント間でデータを共有。
5 provide / inject 親コンポーネントから子コンポーネントにデータやロジックを渡す。

ここでのテーマである v-slot ディレクティブと関連するのが No 3 の slots です。
slots は、親コンポーネントから子コンポーネントに HTML テンプレート等を渡す機能となります。

以下、slots の定義の仕方について、具体例で確認してみましょう。
chapter7-1.html ファイルを作成した上で次のコードを記載してください。

<div id="app">
  <child-component>
    <span style="color: red;">山田</span>
  </child-component>
</div>

<script>
  const { createApp, ref } = Vue

  // 子コンポーネント
  const ChildComponent = {
    template: `<div>私の名前は <slot></slot> です。</div>`
  }

  // 親コンポーネント
  createApp({
    components: {
      ChildComponent
    },
    setup() {
      return {}
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter7-1.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <child-component>
      <span style="color: red;">山田</span>
    </child-component>
  </div>

  <script>
    const { createApp, ref } = Vue

    // 子コンポーネント
    const ChildComponent = {
      template: `<div>私の名前は <slot></slot> です。</div>`
    }

    // 親コンポーネント
    createApp({
      components: {
        ChildComponent
      },
      setup() {
        return {}
      }
    }).mount('#app')
  </script>
</body>
</html>

slots に関連する部分を見ていきましょう。

① slots の指定箇所

slots は子コンポーネントの template 内に <slot></slot> の形で定義します。
この <slot></slot> の部分に親コンポーネントから受け取った HTML テンプレートがレンダリングされます。

template: `<div>私の名前は <slot></slot> です。</div>`

② 親コンポーネントから渡す HTML テンプレート

親コンポーネントから渡す HTML テンプレートは、子コンポーネントのタグ内に記載します。渡されるテンプレートは以下のうち <span style="color: red;">山田</span> の部分になります。

<child-component>
  <span style="color: red;">山田</span>
</child-component>

これが、子コンポーネントの <slot></slot> の部分に差し込まれ、「山田」という文字が赤色で表示されることになります。

③ ブラウザで表示する

ブラウザで確認をしてみましょう。

vue_4-7-1.png

以上のように表示されれば OK です。
slots を使用することで、親コンポーネントから子コンポーネントにテンプレートを渡せることが確認できました。

1-3. v-slot ディレクティブについて

ようやくですが、本題です。
先ほど確認しました slots(スロット)は、1 つの HTML テンプレートを渡すものでした。
ここで、v-slot ディレクティブを使用することにより、slots に名前を付けることができますので、複数の slots を子コンポーネントに渡すことができるようになります。

以下、v-slot ディレクティブの使い方について、具体例で確認していきます。
chapter7-2.html ファイルを作成した上で次のコードを記載してください。

<div id="app">
  <child-component>
    <template v-slot:firstname><span style="color: red;">山田</span></template>
    <template v-slot:age>30</template>
  </child-component>
</div>

<script>
  const { createApp, ref } = Vue

  // 子コンポーネント
  const ChildComponent = {
    template: `<div>
                 私の名前は <slot name="firstname"></slot> です。<br />
                 年齢は <slot name="age"></slot> です。 
               </div>`
  }

  // 親コンポーネント
  createApp({
    components: {
      ChildComponent
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter7-2.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <child-component>
      <template v-slot:firstname><span style="color: red;">山田</span></template>
      <template v-slot:age>30</template>
    </child-component>
  </div>

  <script>
    const { createApp, ref } = Vue

    // 子コンポーネント
    const ChildComponent = {
      template: `<div>
                   私の名前は <slot name="firstname"></slot> です。<br />
                   年齢は <slot name="age"></slot> です。 
                 </div>`
    }

    // 親コンポーネント
    createApp({
      components: {
        ChildComponent
      }
    }).mount('#app')
  </script>
</body>
</html>

v-slot ディレクティブの使用箇所を中心に見ていきましょう。

① v-slot ディレクティブの使用箇所

v-slot は、親コンポーネントから子コンポーネントを呼び出す <child-component> タグ内で使用します。 このタグ内の 2 行が、子コンポーネントの <slot></slot> の部分に差し込まれることになります。

<child-component>
  <template v-slot:firstname><span style="color: red;">山田</span></template>
  <template v-slot:age>30</template>
</child-component>

<template v-slot:firstname> タグで囲まれた <span style="color: red;">山田</span> は、v-slot で firstname という名前が付けられています。
もう一つの <template v-slot:age> タグで囲まれた 30 は、v-slot で age という名前が付けられています。
ここで付けられた名前に従って、子コンポーネントの指定箇所にテンプレートとして差し込まれることになります。

② slots の挿入箇所

子コンポーネントのテンプレートで、親コンポーネントから受け取る slots の挿入箇所を定義しています。

<div>
  私の名前は <slot name="firstname"></slot> です。<br />
  年齢は <slot name="age"></slot> です。 
</div>

挿入箇所は、2 つ定義されています。
1 つ目は、<slot name="firstname"></slot> の部分で、ここには firstname という名前の slots が差し込まれます。
2 つ目は、<slot name="age"></slot> の部分で、ここには age という名前の slots が差し込まれます。

③ ブラウザで表示する

それでは、ブラウザで確認をしてみましょう。

vue_4-7-1-2.png

以上のように表示されると思います。
このように、v-slot ディレクティブを使用することで、複数の slots を指定することが可能となります。

2. v-pre ディレクティブ

v-pre を使用すると、要素内のテキストをそのまま表示することができます。
具体例を見た方が早いので、早速、コードを書いてみましょう。

具体例で確認してみましょう。
chapter7-3.html ファイルを作成した上で次のコードを記載してください。

<div id="app">
  <div v-pre>{{ message }}</div>
</div>
全てのコードを表示
chapter7-3.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-pre>{{ message }}</div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const message = ref('Hello World!')
        return {
          message,
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

v-pre ディレクティブは、<div v-pre>{{ message }}</div> のように、div タグ内に定義しています。

これでブラウザを開いてみましょう。
本来であれば、スクリプト側で定義した文字列「Hello World!」が表示されるはずですが、マスタッシュ構文が無効にされて、そのまま表示されています。

vue_4-7-2.png

以上、v-pre ディレクティブが正しく作用することが確認できました。

3. v-cloak ディレクティブ

v-cloak ディレクティブを使用すると、要素の描画の準備が整うまでの間(アプリケーションインスタンスのコンパイル完了までの間)、指定した CSS を適用することができます。

コンポーネントの規模が大きいアプリケーションの場合、ページの読み込み開始から表示が完了するまでの間、短時間ですが、次のようにマスタッシュ構文のまま画面が表示される時間が生じます。

vue_4-7-2.png

そして、描画が完了したら、次のように正常な表示になります。

vue_4-7-3.png

この画面のちらつきを無くすために使用できるのが、v-cloak ディレクティブとなります。

実際にコードを書くと次のとおりです。
chapter7-4.html ファイルを作成した上、記載してください。

<style>
  [v-cloak] {
    display: none;
  }
</style>

<div id="app">
  <div v-cloak>{{ message }}</div>
</div>
全てのコードを表示
chapter7-4.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <style>
    [v-cloak] {
      display: none;
    }
  </style>

  <div id="app">
    <div v-cloak>{{ message }}</div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const message = ref('Hello World!')
        return {
          message,
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

① v-cloak ディレクティブ

v-cloak ディレクティブの指定箇所は次のところです。
この {{ message }} の描画準備が整うまでは、v-cloak に指定された CSS が適用されます。

<div v-cloak>{{ message }}</div>

② CSS の定義

v-cloak に指定した CSS は次のところです
この CSS が適用されている間は、画面には表示されないことになります。

<style>
  [v-cloak] {
    display: none;
  }
</style>

③ ブラウザで表示する

確認はできないかもしれませんが、ブラウザで表示してみましょう。

vue_4-7-3.png

実際には、一瞬 v-cloak ディレクティブが適用されていることになります。

4. v-once ディレクティブ

v-once ディレクティブを使用した要素は 1 回のみレンダリングされます。
それ以降の再レンダリングでは、静的コンテンツとして扱われ、表示の変更は行われなくなります。

早速コードを書いてみましょう。
chapter7-5.html ファイルを作成した上、次のように記載してください。

<div id="app">
  <div v-once>{{ message }}</div>
  <div>{{ message }}</div>
  <button @click="changeMessage">変更</button>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const message = ref('Hello World!')
      const changeMessage = () => message.value = 'こんにちは'
      return {
        message,
        changeMessage
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter7-5.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-once>{{ message }}</div>
    <div>{{ message }}</div>
    <button @click="changeMessage">変更</button>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const message = ref('Hello World!')
        const changeMessage = () => message.value = 'こんにちは'
        return {
          message,
          changeMessage
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

以下の 2 行は、マスタッシュ構文で message の内容を表示するものですが、1 行目のみ v-once ディレクティブを使用しています。

<div v-once>{{ message }}</div>
<div>{{ message }}</div>

v-once を付けた 1 行目は、最初のレンダリングのみ値を反映し、それ以降は変化しないこととなります。

ブラウザで確認してみましょう。
最初の読み込み時には、どちらも message の初期値「Hello World!」がレンダリングされています。

vue_4-7-4.png

続いて「変更」ボタンをクリックして message の値を変更してみましょう。
すると次のように表示されます。

vue_4-7-5.png

v-once を付けた 1 行目の要素は再レンダリングされないことが確認できたと思います。
v-once ディレクティブは更新パフォーマンスの最適化などで使用されます。

5. v-memo ディレクティブ

v-memo ディレクティブは、Vue 3.2 から導入された新しい機能です。
この v-memo ディレクティブを使用することで、再レンダリングの際の条件を指定することができます。 これにより、不要なレンダリングを行わず更新パフォーマンスを上げることができます。

5-1. v-memo ディレクティブの基本構文

v-memo ディレクティブの基本構文は次のとおりです。

<div v-memo="[valueA, valueB]">
  /* valueA, valueB の値が変更されていなければこの部分の更新は省略される */
</div>

再レンダリング時に、v-memo ディレクティブで指定した valueAvalueB の値が直前のレンダリング時と同じであれば、div タグ内の更新は省略されます。
v-memo で指定された [valueA, valueB] は配列のため、[valueA, valueB, valueC ...] のように要素はいくつでも指定できます。

5-2. v-memo ディレクティブの具体例

それでは v-memo ディレクティブを使用したコードを書いて動作を確認してみましょう。
chapter7-6.html ファイルを作成した上、次のように記載してください。

<div id="app">
  <div v-for="id in ids" :key="id" v-memo="[id === selected]">
    <input type="radio" v-model="selected" :value="id">ID: {{ id }} 選択: {{ id === selected }}
  </div>
</div>

<script>
  const { createApp, ref } = Vue
  createApp({
    setup() {
      const selected = ref(null)
      const ids = ref([0, 1, 2, 3, 4, 5, 6, 7, 8 ,9])
      return {
        selected,
        ids
      }
    }
  }).mount('#app')
</script>
全てのコードを表示
chapter7-6.html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <script src="https://unpkg.com/vue@3.2.36"></script>

  <div id="app">
    <div v-for="id in ids" :key="id" v-memo="[id === selected]">
      <input type="radio" v-model="selected" :value="id">ID: {{ id }} 選択: {{ id === selected }}
    </div>
  </div>

  <script>
    const { createApp, ref } = Vue
    createApp({
      setup() {
        const selected = ref(null)
        const ids = ref([0, 1, 2, 3, 4, 5, 6, 7, 8 ,9])
        return {
          selected,
          ids
        }
      }
    }).mount('#app')
  </script>
</body>
</html>

これは、ids に含まれる ID の数だけ、ラジオボタンのリストを作るコードとなります。
v-memo="[id === selected]" で指定される配列要素は id === selected の 1 つのみとなります。

<div v-for="id in ids" :key="id" v-memo="[id === selected]">
  <input type="radio" v-model="selected" :value="id">ID: {{ id }} 選択: {{ id === selected }}
</div>

つまり、ラジオボタンの選択状況が変更された要素のみ再レンダリングをするという指定になっています。

ブラウザを開いてみましょう。
最初の読み込み時は、ID が 0 から 9 までの全ての行がレンダリングされています。

vue_4-7-6.png

続いて ID の 3 を選択します。
以下のように、ラジオボタンが切り替わりますが、ここで再レンダリングされるのは、赤枠の 2 行分となります(目には見えませんが)。

vue_4-7-7.png

この例は 10 行のみのため、効果自体は少ないかもしれません。
しかし、リストが 1000 行以上などとなる場合には、v-memo を使用することの効果が大きくなります。
このようなディレクティブがあることも把握しておきましょう。

本レッスンは以上となります。
ディレクティブは、Vue の大事な機能の 1 つですので、分からなくなったら復習をしつつ、使いこなせるようにしていきましょう。