😽

Vue v-for使用法

2024/11/04に公開

今回はv-forで配列の各要素を簡単に描画する方法について解説していきます。

これはJavaScriptのfor文のような使い方になってまして、値には好きな配列名 in 配列という書き方になります。

<script setup>
import { ref } from 'vue'
const nyans = ref(['kuro','tora','mike','tama'])
</script>

<template>
 <li v-for="nyan in nyans">{{ nyan }}</li>
</template>

v-for="neko in Nyans"の左側の部分は何でもいいのですが、配列名が複数形で書かれている場合は単数系だとわかりやすいでしょう。
 こうすることでこのnyanという変数をリストタグの中で使うことができます。
 上記のように書くとリストが表示されます。

const nyansの配列が全て表示されていますね。
 今は<li>タグですが<div>タグでも可能です。
 このような配列の各要素を一気にリストレンダリングするのがv-forディレクティブになります。

で、このままだとESLintで赤い波線エラーが出てしまいますが、これはkey属性が設定されていないからです。
 Vueではkey属性という特別な属性が用意されていて、これはHTMLに存在する属性ではありません。
 このkey属性は文字列でも数字でも入れることができて、何の為にあるかというとVueが内部的にレンダリングする際のヒントとして使うためだけに存在します。
 どう使うかというと、具体例ですが<li>タグの中に<input>タグを置きます。

<template>
  <li v-for="nyan in nyans"><input type="text" />{{ nyan }}</li>
</template>

そしてボタンを用意して

<template>
  <button @click="nyans.shift()">button</button>
  <li v-for="nyan in nyans"><input type="text" />{{ nyan }}</li>
</template>

こうするとボタンを押すと先頭が消えるので押していくと上から順に消えていくというコードを作ります。

で、ここからが重要ですが、この<input>タグにそれぞれ値を入れます。
値を入れてからボタンを押していくと消えるのですが、<input>タグで入れた値と配列の値がずれていきます。




なぜこうなってしまうのか?
 Vueでは画面をデータが更新されて再レンダリングする時に可能な限り効率的に要素の移動が最小限に収まるように変更を加えています。
 今回の例だとこの画面からボタンを押してこの画面になった時、要素の移動が最小限に収まるように変更し、Vueではそれぞれの要素の違いをタグの種類だけで判断しています。
なので同じタグであれば全て同じものとして捉えられ、違いは無いという認識をされてしまいます。
今回の場合はボタンを押されていった状態に変わって行った時はそれぞれの<li>タグの違いは無いので、実は先頭の<li>タグが消えているわけではなく、一番下の<li>タグが消えて<input>タグの中のテキストの部分だけが更新されているという動きをしています。
なのでこのようなズレが生じてしまいます。
この動きのおかげで非常に効率的に動きが速くなっていますが、時によっては同じタグでもそれぞれが別のものであるという認識をさせたい時もあります。
 こういう時にkey属性でそれぞれに識別子を与えますが、静的なデータだと全部同じになってしまうのでv-bindを使用します。
で、key属性とv-forを同じタグで使用するときは先にv-forの処理が行われ、その後にkey属性の処理が行われるのでkey属性の値の中ではv-forで作られたnyanという変数を使うことができます。
 この場合にはkey属性に配列の中の変数の文字列が指定されます。

<template>
  <button @click="nyans.shift()">button</button>
  <li v-for="nyan in nyans" :key="nyan"><input type="text" />{{ nyan }}</li>
</template>

こうすることで変数がkeyとなり紐付けできるので、ボタンを押すと<input>タグと同じものが消えていきます。
 これがkey属性ですね。
 非常にややこしいですが、基本的にv-forを使うときは今のように予期せぬバグを避けるためにkey属性は必ずセットで付けるようにESLintでエラーが出るようになっていますのでv-forとkey属性はセットで使うということを覚えておきましょう。

一つ注意点ですが、key属性の値にはループする上で他と被らない値にする必要があります。
例えば今回なら
const nyans = ref(['kuro', 'tora', 'mike', 'tama'])
この配列内に同じ'kuro'が2つあったらkey属性が被ってしまいますので、<input>タグにkuro1,kuro2があった場合はうまく動きません。
なのでkey属性の値は必ずユニークな値を指定する必要があります。
今回の場合であれば配列の要素をオブジェクトに変えるというのも一つの手です。

<script setup>
import { ref } from 'vue'
const nyans = ref([
  { id: 1, name: 'kuro' },
  { id: 2, name: 'kuro' },
  { id: 3, name: 'tora' },
  { id: 4, name: 'mike' },
  { id: 5, name: 'tama' }
])
</script>

<template>
  <button @click="nyans.shift()">button</button>
  <li v-for="nyan in nyans" :key="nyan.id"><input type="text" />{{ nyan.name }}</li>
</template>

このようにするときちんとidで紐つけられているので上から順に消えていきます。



なのでいろいろ工夫してkey属性にはユニークな値を指定するようにしましょう。

次はこのループのINDEX何回目のループかを取得する方法について解説します。
v-forでループする時に、そのループが何回目かの情報を取得することができます。
やり方としては、inの左側の(今回だとnyan)ところを()で囲います。
そして『,』を付けて好きな名前(一般的にindexを使用)を書きます。

 <li v-for="(nyan, index) in nyans" :key="nyan.id"><input type="text" />{{ nyan.name }}</li>

こうするとindexという変数が作られて、その中にこのループが何周目かという情報が入るようになります。
 このindexもnyanと同じようにリストタグで使用することができるようになり、例えば最後に表示させてみます。

 <li v-for="(nyan, index) in nyans" :key="nyan.id">
    <input type="text" />{{ nyan.name }}({{ index }})
  </li>


こうすることでそのループの回数の情報を表示することができます。
 ちなみに、key属性にindexを入れることはできません。
nyanはindexと常に同じもの同士が結びついているわけではないからです。
ボタンを押して消していくとindexが変わっていくのがわかるかと思います。
なのでkey属性にindexは入れない方がいいです。

では次はv-forで分割代入する方法です。
v-forでもinの左側nyanの部分にオブジェクトが入る場合はJavaScriptの分割代入のような書き方をすることができます。

<li v-for="({ id, name }, index) in nyans" :key="id">
    <input type="text" />{{ name }}({{ index }})
  </li>

こうすることでkey属性にそのままidを入れれますし、{{ nyan.name }}もnameで済みます。先ほどのコードと同じ意味になります。
ちなみに()やindexがなくても問題なく使えます。

<template>
  <button @click="nyans.shift()">button</button>
  <li v-for="{ id, name } in nyans" :key="id">
    <input type="text" />{{ name }}({{ index }})
  </li>
</template>

次は<template>タグをv-forと一緒に使う方法です。

<template>
  <button @click="nyans.shift()">button</button>
  <template v-for="({ id, name }, index) in nyans" :key="id">
    <p>id:{{ id }}</p>
    <input type="text" />{{ name }}({{ index }})
  </template>
</template>

v-ifの時と同様に複数の要素をまとめてループさせたいけど<div>タグを作りたくない場合、<template>タグを使用することもできます。
非常に便利ですし、これも同じように最終的には<template>タグは消えます。

<template>タグだったとしてもkey属性は必ず付けるようにしてください。
また、v-forとv-ifを同時に同じ要素に使った場合はv-ifの処理の方が先に実行されますので、v-forの値の中で作られるidなどの変数にはアクセスできません。
一応、v-forとv-ifを同時に使えますがかなりややこしくなってしまうので一緒に使うのは避けた方がいいです。
なので同時に使いたい場合はわかりやすいように新しく要素を一つ作って、ずらして適用させましょう。
こういう時も余分なタグは作りたくないので<template>タグを使うのがいいでしょう。

<template>
 <button @click="nyans.shift()">button</button>
 <template v-for="({ id, name }, index) in nyans" :key="id">
   <template v-if="id === 2">
     <p>id:{{ id }}</p>
     <input type="text" />{{ name }}({{ index }})
   </template>
 </template>
</template>

こうすることでこの場合はv-ifがv-forの後に来てidも使えますので<template>タグはv-ifやv-forを複数で使うのに適しています。

では次は配列ではなくオブジェクトをそのままv-forに適用させる方法です。
下記のようなcatのオブジェクトがあった場合、valueで引き出せます。

<script setup>
import { ref } from 'vue'
const cat = ref({
  name: 'Nyan',
  age: 7,
  type: 'osu'
})
</script>

<template>
  <p v-for="value in cat" :key="value">{{ value }}</p>
</template>

で、value以外に()を書いてオブジェクトのkeyやそのループが何周目かを表すindexも取得できます。

<p v-for="(value, key, index) in cat" :key="value">{{ key }}:{{ value }}({{ index }})</p>

これで画面を見てみると

このようにちゃんとvalue, key, indexが表示されます。

次はv-forの右側をオブジェクトではなく数字を入れた時の動きについてです。
v-forのinの右側の部分には配列やオブジェクト以外にもこのように数字を入れることができます。

template>
  <p v-for="n in 10" :key="n">{{ n }}</p>
</template>

この場合は単純にその回数分ループしてレンダリングしてくれるようになりますし、inの左側にはそのループが何周目かというデータが入ります。
仮にnとしてkey属性にもnを入れて表示させます。

注意点としてはindexと違い0から始まらず1からなのでそこは注意して使いましょう。

最後にinの代わりにofを使ってもどちらでもOKです。

長々とv-forについていろいろ書きましたが、使いこなすと連続で使用するものについてかなり楽になるのでぜひマスターしましょう。

次回からはcomponentsの使用方法です。

Discussion