制御構造とイベントを処理するディレクティブ
Vue.jsのディレクティブを利用すると、繰り返し表示や条件による表示切替、イベントハンドラーの定義を行うこともできます。これらの記述方法を、図3のサンプルで説明します。図1のサンプルではデータを1組だけ入力できましたが、図3のサンプルでは入力された複数個のデータが表で表示されます。また、データの個数によって、タイトルの文言が変化します。
実装内容を説明していきます。<script>部はリスト3の通りです。
<script lang="ts"> import { defineComponent } from "vue" // TypeScriptのインタフェース ...(1) interface Phone { no: number company: string name: string url: string weight: number } export default defineComponent({ // データ ...(2) data() { return { phones: [] as Array<Phone>, // Phoneインタフェース配列を型指定 ...(2a) no: 1, company: 'Apple', name: 'iPhone 13', url:'https://www.apple.com/' as string, weight: 173 as number } }, methods: { onAddButtonClicked() { // 追加ボタン押下時の処理 ...(3) // Phoneインタフェースに適合したオブジェクトが設定できる this.phones.push({ no: this.no, company: this.company, name: this.name, url: this.url, weight: this.weight }) }, onDelButtonClicked() { // 削除ボタン押下時の処理 ...(4) this.phones.pop() // 最終要素を削除 } } }) </script>
まず、入力内容をまとめて扱うために、(1)でPhone型のインタフェースを宣言します。インタフェースはオブジェクトが持つべきプロパティやメソッドを記述する仕組みで、ここでは入力項目に対応するno、company、name、url、weightのプロパティを指定します。このインタフェースを利用してデータ部(2)では、複数データを保持するphones配列を定義しています。
(2)の「as Array<Phone>」記述は、Phone型の配列であることを表します。このように配列の型を定めておくことで、追加ボタン押下時の処理(3)で、phones配列にPhone型以外のオブジェクトが代入されるのを防げます(後述の補足を参照)。削除ボタン押下時の処理(4)では、phones配列のpopメソッドにより、配列の最終要素を削除します。
なお、(3)のpushメソッドや(4)のpopメソッドが実行されるときに、配列の変更を画面に反映するため、Vue.jsではこれらのメソッドをラップします。ラップされる配列のメソッドは公式ドキュメントを参照してください。
[補足]インタフェースによる配列要素の型指定
リスト3ではPhoneインタフェース(1)により、phones配列要素の型を指定します。そのため、追加時の処理(3)でPhonesインタフェースに一致しないオブジェクトは代入できません。例えば、Phoneインタフェースに存在しない余分な変数(図4のis5G)を含むオブジェクトをphone配列に代入しようとするとエラーになります。
リスト3に対応する<template>部の実装はリスト4の通りです。
<template> (略) <!-- ボタンとイベントハンドラー ...(1)--> <div> <button v-on:click="onAddButtonClicked"> <!-- v-onでボタン押下時の処理を指定 ...(1a)--> 追加 </button> <button @click="onDelButtonClicked"> <!-- v-onの省略記法 ...(1b)--> 削除 </button> </div> <hr/> <!-- v-if、v-else-if、v-elseの記法 ...(2)--> <!-- phones配列の長さが0のとき --> <h1 v-if="phones.length == 0"></h1> <!-- phones配列の長さが1のとき --> <h1 v-else-if="phones.length == 1">電話の登録ありがとう</h1> <!-- phones配列の長さが2のとき --> <h1 v-else-if="phones.length == 2">2台持ちとはなかなか</h1> <!-- phones配列の長さがそれ以外(3以上)のとき --> <h1 v-else>3台以上持っているの!?</h1> <table> <thead> <tr> <th>番号</th> <th>会社</th> <th>機種</th> <th>重量</th> <th>URL</th> </tr> </thead> <tbody> <!-- v-forの記法 ...(3)--> <tr v-for="phone in phones" v-bind:key="phone.no"> <td>{{ phone.no }}</td> <td>{{ phone.company }}</td> <td>{{ phone.name }}</td> <td>{{ phone.weight }} </td> <td>{{ phone.url }} </td> </tr> </tbody> </table> </template>
(1)では、ボタンを表す<button>タグに、v-onディレクティブでイベントハンドラーを指定しています。「v-on:<イベント名>」で、イベントに対応して実行されるメソッドを指定します。ここでは(1a)の「追加」ボタン押下時にonAddButtonClickedメソッドが、(1b)の「削除」ボタン押下時にonDelButtonClickedメソッドが、それぞれ実行されます。(1b)の「@click」は「v-on:click」の省略記法です。
(2)では、phones配列の長さにより表示を変える処理を、v-if、v-else-if、v-elseの各ディレクティブで記述しています。v-if、v-else-ifディレクティブに条件を設定すると、条件に合致したときだけその要素が表示されます。ここではphones.lengthの長さが0、1、2、それ以外(3以上)のそれぞれの場合に表示される内容を記述しています。
(3)では、phones配列の各要素に対して表の行を表示する処理を、v-forディレクティブで記述しています。<tr>タグに設定したv-forディレクティブの「phone in phones」記述により、phones配列の各要素をphone変数で参照できるようになるため、配下の<td>タグでphone変数のプロパティを画面に表示しています。
なお、v-forディレクティブを使用する場合は、Vue.jsにv-for配下の各ノードを識別させて、部分的な再描画などの効率的な処理を行うため、配列要素を一意識別するデータをv-bind:keyディレクティブに指定することが推奨されます。このサンプルではphone.noを指定しています。
v-forとv-ifの組み合わせでリストのフィルター処理を実現
v-forとv-ifのディレクティブを組み合わせると、複数のデータからフィルターして表に表示する処理が実現できます。このサンプル(図5)では、図3のサンプルをもとに、重量170g以下のスマートフォンのみを表示するチェックボックスを付けました。
まず、チェックボックスの記述を<template>部にリスト5の通り追加します。
<input type="checkbox" v-model="checked" />170g以下のスマホのみ表示
チェックボックスに紐づける変数checkedを、<script>部にリスト6の通り追加します。
data() { return { (略) checked: false // チェックボックスに対応 } },
表を表示する<template>部の記述は、リスト7の通りとなります。
<tbody> <!-- v-forの記法(templateタグを使用)...(1) --> <template v-for="phone in phones" v-bind:key="phone.no"> <!-- v-ifでtrタグの表示有無を制御 ...(2)--> <tr v-if="!checked || phone.weight <= 170"> <td>{{ phone.no }}</td> <td>{{ phone.company }}</td> <td>{{ phone.name }}</td> <td>{{ phone.weight }} </td> <td>{{ phone.url }} </td> </tr> </template> </tbody>
(1)の<template>タグは、特定のHTMLタグには対応せず、配下の要素に対してディレクティブの効果を適用できる、Vue.jsのタグです。ここでは配下の<tr>タグに対してv-forディレクティブによる繰り返し処理を適用させるために利用しています。
一方で配下の<tr>タグ(2)では、v-ifディレクティブにより、チェックボックスが未チェック(全件表示に対応)、またはphone.weightが170以下の場合のみ表示するよう設定しています。この記述で、v-ifディレクトリによる条件設定を適用しつつ、v-forディレクトリによる繰り返し処理を適用できます。
[補足]v-forとv-ifを1つのタグに書かないわけ
リスト7で<template>を使わなくても、リスト8の通りv-forとv-ifディレクティブを1つのタグに両方書けばよいと思うかもしれません。
<tr v-for="phone in phones" v-if="phone.weight <= 170">
このような記述はVue.jsで行うべきではありません。理由はVue 3とVue 2で以下の通り異なりますが、行うべきでないのは共通しています。
- Vue 3では、v-ifはv-forより優先度が高いため、v-forで(v-ifより後に)定義されるphones配列要素を表すphone変数を、v-ifで参照できません。
- Vue 2では、v-forはv-ifより優先度が高いですが、1つのタグにv-forとv-ifを共存させることで、データ変更時の再レンダリング時にリスト全体が処理されてパフォーマンスが低下します。
まとめ
本記事では、Vue.jsの基本的な記述法を説明しました。<script>部で記述したデータや算出プロパティ、メソッドを、<template>部で{{ }}記述やディレクティブを用いて画面に反映します。TypeScriptを利用することで、Vue.jsでも型を意識した実装ができます。
今回はページ全体に対応するApp.vueを直接変更していきましたが、Vue.jsではページの一部分を別のコンポーネントに分割して記述できます(App.vueは「ページ全体に対応するコンポーネント」です)。
次回は、ページを複数のコンポーネントに分割して、データをやり取りする方法を説明していきます。また、今まで利用していた記法(Options API)以外にも、クラス形式、Composition APIといった記法を併せて説明していきます。