SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

TypeScriptで学ぶJavaScriptフレームワーク「Vue.js」の利用法

Vue.jsの基本記法を学んでTypeScriptで自在に処理をコーディングしよう

TypeScriptで学ぶJavaScriptフレームワーク「Vue.js」の利用法 第4回

  • X ポスト
  • このエントリーをはてなブックマークに追加

制御構造とイベントを処理するディレクティブ

 Vue.jsのディレクティブを利用すると、繰り返し表示や条件による表示切替、イベントハンドラーの定義を行うこともできます。これらの記述方法を、図3のサンプルで説明します。図1のサンプルではデータを1組だけ入力できましたが、図3のサンプルでは入力された複数個のデータが表で表示されます。また、データの個数によって、タイトルの文言が変化します。

図3 繰り返し・条件判定・イベントハンドラーを実装したサンプル(p002-if-for-on1)
図3 繰り返し・条件判定・イベントハンドラーを実装したサンプル(p002-if-for-on1)

 実装内容を説明していきます。<script>部はリスト3の通りです。

[リスト3]図3のサンプルの<script>部(p002-if-for-on1/src/App.vue)
<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配列に代入しようとするとエラーになります。

図4 Phoneインタフェースとの型不一致によるエラー(p002-if-for-on1)
図4 Phoneインタフェースとの型不一致によるエラー(p002-if-for-on1)

 リスト3に対応する<template>部の実装はリスト4の通りです。

[リスト4]図3のサンプルの<template>部(p002-if-for-on1/src/App.vue)
<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以下のスマートフォンのみを表示するチェックボックスを付けました。

図5 表のデータをフィルターするサンプル(p003-if-for-on2)
図5 表のデータをフィルターするサンプル(p003-if-for-on2)

 まず、チェックボックスの記述を<template>部にリスト5の通り追加します。

[リスト5]フィルター適用を切り替えるチェックボックス(p003-if-for-on2/src/App.vue)
<input type="checkbox" v-model="checked" />170g以下のスマホのみ表示

 チェックボックスに紐づける変数checkedを、<script>部にリスト6の通り追加します。

[リスト6]チェックボックスに対応するchecked変数(p003-if-for-on2/src/App.vue)
data() {
  return {
(略)
    checked: false // チェックボックスに対応
  }
},

 表を表示する<template>部の記述は、リスト7の通りとなります。

[リスト7]表を表示する<template>部(p003-if-for-on2/src/App.vue)
<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つのタグに両方書けばよいと思うかもしれません。

[リスト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 3Vue 2)を参照してください。

まとめ

 本記事では、Vue.jsの基本的な記述法を説明しました。<script>部で記述したデータや算出プロパティ、メソッドを、<template>部で{{ }}記述やディレクティブを用いて画面に反映します。TypeScriptを利用することで、Vue.jsでも型を意識した実装ができます。

 今回はページ全体に対応するApp.vueを直接変更していきましたが、Vue.jsではページの一部分を別のコンポーネントに分割して記述できます(App.vueは「ページ全体に対応するコンポーネント」です)。

 次回は、ページを複数のコンポーネントに分割して、データをやり取りする方法を説明していきます。また、今まで利用していた記法(Options API)以外にも、クラス形式、Composition APIといった記法を併せて説明していきます。

参考資料

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
TypeScriptで学ぶJavaScriptフレームワーク「Vue.js」の利用法連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト  吉川 英一(ヨシカワ エイイチ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/15267 2021/12/07 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング