Hanatare's PaPa

Make life a little richer.

Virtual Space of Hanatare's PaPa

人生をほんの少しだけ充実させる

【Vue.js 3】ディレクティブを理解する - リストレンダリングと属性の動的制御 -

前回の記事では、条件に応じて要素の表示を切り替える「v-if」と「v-show」について紹介しました。

www.hanatare-papa.jp

今回は、配列やオブジェクトに格納されたデータを元に要素を繰り返し描画する「v-for」とHTML属性(srcやclassなど)をVueのデータと動的に連携させる「v-bind」について記事にしたいと思います。前提としてこの記事では、Vue3での記法を前提とします。

記事のポイント
  • v-forについて理解する。
  • v-bindについて理解する。

v-for:繰り返し処理を行う要素

Webアプリケーションでは、「ユーザ一覧」、「商品リスト」、「ToDoリスト」のように同じ構造の要素をデータの数分繰り返し表示する場面が頻繁にあります。そのような繰り返し表示を行う際に、この「v-for」は非常に有効です。

v-forの基本的な使い方

<script setup>
import { ref } from 'vue'

const products = ref([
  { id: 1, name: 'リンゴ', price: 150 },
  { id: 2, name: 'バナナ', price: 100 },
  { id: 3, name: 'オレンジ', price: 120 },
])
</script>
<template>
  <ul>
    <li v-for="product in products" :key="product.id">
      {{ product.id }}:{{ product.name }} - {{ product.price }}円
    </li>
  </ul>
</template>

このコードは、products配列の各要素に対して「li」タグを生成します。「v-for」が配列の要素の数だけ自動的に「li」を繰り返してくれるので、HTMLに何度も同じようなタグを書く必要がありません。「v-for」では要素のインデックスを2つ目の引数として取得することができます。

<li v-for="(product, index) in products" :key="product.id">
  {{ index + 1 }}. {{ product.name }}
</li>

オブジェクトの繰り返し

「v-for」は配列だけでなく、オブジェクトのプロパティを繰り返すこともできます。

<script setup>
import { ref } from 'vue'

const user = ref({
  author: 'Hanatare',
  age: 30,
  blog: 'Hanatare',
})
</script>
<template>
  <ul>
    <li v-for="(value, key, index) in user" :key="value">({{ index }} ){{ key }}:{{ value }}</li>
  </ul>
</template>

このコードは、userというオブジェクトの各要素に対して「li」タグを生成します。valueは要素の値、keyは要素のKey値、indexは何回目の繰り返しかを表しています。

範囲の繰り返し

整数を指定して、決まった回数だけブロックを繰り返すことも可能です。

<script setup>
import { ref } from 'vue'
</script>
<template>
  <ul>
    <li v-for="n in 10" :key="n">({{ n }} )</li>
  </ul>
</template>

上記のコードは、inの後ろに10と書いて10回繰り返すようにしています。そして、繰り返し回数の今が何回目かをnに代入している処理になります。この時nは0からではなく1から始まることに注意が必要です。

key属性はVueに「個」を認識させるための鍵

「v-for」を使う上で、非常に重要なことが、「key」属性です。「v-for」で要素をレンダリングする際、各要素にユニーク「key」を「v-bind:key」(または省略系の「:key」)で指定する必要があります。

↓v-bindで記述

<li v-for="item in items" v-bind:key="item.id">
  {{ item.text }}
</li>

↓が省略形

<li v-for="item in items" :key="item.id">
  {{ item.text }}
</li>

「key」をつけないといけない理由は、Vueが変更を効率的に追跡するための「ヒント」としてkeyを使用するからです。

「key」がない場合、Vueは「その場限りのパッチ」戦略をとります。例えばリストの順番が変わった時、Vueは要素を並べ替えるのではなく、各要素の中身だけを新しいデータで上書きしようとします。これは一見効率的に見えますが、フォームの入力欄など、要素が内部的な状態を持つ場合に深刻なバグを引き起こします。例えば、リストの2番目の項目を削除したのに、見た目上は3番目の項目の入力内容が2番目にスライドしてきて、最後の入力欄が消える、といった奇妙な挙動が起こります 。

「key」を正しく設定すると、Vueは各要素を個別のものとして正確に識別できるようになります。「key」は、Vueの内部的な仮想DOM(VDOM)の差分検出アルゴリズムに対する直接的な指示書なのです。これにより、Vueは要素の並べ替えや削除を賢く、そして効率的に行うことができ、パフォーマンスの向上とバグの防止に繋がります。

key属性のよくある間違い

keyには、必ずリスト内で一意(ユニーク)で、かつ安定した値(文字列や数値)を指定する必要があります。データベースから取得したデータのidなどが最適です。

よくある間違いとして、配列のindexをkeyに指定する例がありますが、これは避けるべきです。リストの順番が入れ替わったり、途中に要素が追加・削除されたりするとインデックスは簡単に変わってしまい、keyが本来の役割を果たせなくなるからです。

v-forとv-ifの併用は要注意

「v-for」と「v-if」を同じ要素に記述することは推奨されていません

Vue 3では、同じ要素にこの2つが存在する場合、「v-if」が「v-for」よりも先に評価されます。つまり、「v-if」の条件式の中では、まだ「v-for」で定義されたitemのような変数にアクセスできず、エラーになってしまいます。

リストの中から特定の条件に合うものだけを表示したい場合は、「v-if」を「v-for」と併用するのではなく、「template」でラップする、もしくは算出プロパティ(Computed Property)を使うという方法があります。

「template」でラップする

<ul>
  <template v-for="user in users" :key="user.id">
    <li v-if="user.isActive">
      {{ user.name }}
    </li>
  </template>
</ul>

「v-for」を「template」タグに移動し、その内側で「v-if」を使います。これにより、まずv-forが実行され、その各要素に対して「v-if」が適用されます 。

算出プロパティ(Computed Property)を使う

// script setup内
const activeUsers = computed(() => {
  return users.value.filter(user => user.isActive)
})
<li v-for="user in activeUsers" :key="user.id">
  {{ user.name }}
</li>

表示したいリストをあらかじめフィルタリングする算出プロパティを作成し、その結果に対して「v-for」を適用します。これは多くの場合、テンプレートをクリーンに保つための最良のアプローチです 。

v-bind:HTML属性をVueデータと連携させる

「v-bind」はHTMLタグが持つ属性(href, src, class, styleなど)の値を、Vueのデータと連携させたい場合に使います。画像のURL、リンク先、要素の有効/無効状態などを、Vueのデータに基づいて変更したい場合に使用します。

基本的なバインディングと省略形の書き方

v-bind:属性名という構文で、HTML属性をVueのデータプロパティにバインドします 。

<img v-bind:src="imageUrl">
<a v-bind:href="pageUrl">リンク</a>

「v-bind」は非常によく使われるため、便利な省略形が用意されています。それがコロン (:) です。「v-bind:href」は「:href」と書くことができます。実際の開発では、ほぼ常にこの省略形が使われます 。  

<script setup>
import { ref } from 'vue'

const url = ref('[https://vuejs.org/](https://vuejs.org/)')
const imagePath = ref('./images/logo.png')
</script>

<template>
  <a :href="url">リンク先</a>
  <img :src="imagePath" alt="動的な画像" />
</template>

class属性とstyle属性の動的バインディング

「v-bind」は、「class」と「style」などの属性に対して使われる際に、さらに特別な機能強化が提供されています。文字列を結合するのではなく、オブジェクトや配列を使って、より直感的かつ柔軟にクラスやスタイルを制御できます。

class属性のバインディング

<script setup>
import { ref } from 'vue'

const isActive = ref(true)
const hasError = ref(false)
</script>

<template>
  <div :class="{ active: isActive, 'text-danger': hasError }">メッセージ</div>
</template>

オブジェクト構文を使うと、特定の条件がtrueのときだけクラスを適用できます。このコードでは、isActiveがtrueなのでactiveクラスが適用されます。hasErrorがtrueになればtext-dangerクラスも追加されます。

class属性の指定の方法

<script setup>
import { ref } from 'vue'
const className = ref('red')
</script>
<template>
  <div :class="{ red: true, 'bg-bule': false }">Morning</div>
  <div :class="[className, { 'bg-blue': true }]">Hello</div>
  <!--空文字は属性を消さない-->
</template>
<style>
.red {
  color: red;
}
.bg-blue {
  background-color: blue;
}
</style>

上記のコードでは、Morningのdivタグに対してredまたは、bg-blueというクラスを付けるかどうかを真偽値で判定しています。HelloのdivタグにはclassNameというrefオブジェクトを配列で持たせています。この場合defaultはtureになります。

style属性のバインディング

<script setup>
import { ref } from 'vue'

const activeColor = ref('red')
const fontSize = ref(20)
</script>
<template>
  <div :style="{ color: activeColor, fontSize: fontSize + 'px' }">動的なスタイル</div>
</template>

スタイルもオブジェクト構文で記述でき、CSSプロパティ名をキャメルケース(fontSize)またはケバブケース('font-size')で指定します。これにより、JavaScriptのデータに基づいてインラインスタイルを動的に設定できます。

v-bindの属性値のTips

v-bindで属性を消したい場合は、nullやundifinedという値を属性値に設定すると、その属性を消すことができます。ただし、空文字を設定した場合は、空文字として認識されてしまい属性は消えません。

#まとめ
今回は、データリストを自在に操る「v-for」と、HTML属性をリアクティブにする「v-bind」について学びました。特にkey属性の重要性と、class/styleバインディングの方法は頻繁使う構文です。今回の記事がVue.jsを使う方の参考になれば幸いです。