リストレンダリング

最終更新日: 2019年4月8日

v-for で配列に要素をマッピングする

配列に基づいて、アイテムのリストを描画するために、v-for ディレクティブを使用することができます。v-for ディレクティブは item in items の形式で特別な構文を要求し、items はソースデータの配列で、item は配列要素がその上で反復されているエイリアスです:

<ul id="example-1">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>
var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

結果:

v-for ブロック内では、親スコープのプロパティへの完全なアクセスを持っています。また v-for は現在のアイテムに対する配列のインデックスを、任意の2つ目の引数としてサポートしています。

<ul id="example-2">
  <li v-for="(item, index) in items">
    {{ parentMessage }} - {{ index }} - {{ item.message }}
  </li>
</ul>
var example2 = new Vue({
  el: '#example-2',
  data: {
    parentMessage: 'Parent',
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

結果:

また、区切り文字として in の代わりに of を使用することができます。これは JavaScript のイテレータ構文に近いものです:

<div v-for="item of items"></div>

オブジェクトの v-for

オブジェクトのプロパティに対して、v-for を使って反復処理することができます。

<ul id="v-for-object" class="demo">
  <li v-for="value in object">
    {{ value }}
  </li>
</ul>
new Vue({
  el: '#v-for-object',
  data: {
    object: {
      firstName: 'John',
      lastName: 'Doe',
      age: 30
    }
  }
})

結果:

2 つ目の引数として key も提供できます:

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

index も提供できます:

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

オブジェクトを反復処理するとき、順序は Object.keys() の列挙順のキーに基づいており、全ての JavaScript エンジンの実装で一貫性が保証されていません

key

Vue が v-for で描画された要素のリストを更新する際、標準では “その場でパッチを適用する” (in-place patch) 戦略が用いられます。データのアイテムの順序が変更された場合、アイテムの順序に合わせて DOM 要素を移動する代わりに、 Vue は各要素にその場でパッチを適用して、その特定のインデックスに何を描画するべきかを確実に反映します。これは Vue 1.x にあった機能の track-by="$index" に似たものです。

この標準のモードは効率がいいです。しかしこれは、描画されたリストが子コンポーネントの状態や、一時的な DOM の状態に依存していないときにだけ適しています (例: フォームのインプットの値)

Vue が各ノードの識別情報を追跡できるヒントを与えるために、また、先ほど説明したような既存の要素の再利用と並び替えができるように、一意な key 属性を全てのアイテムに与える必要があります。この特別な属性は 1.x の track-by に相当するものですが、しかしこれは属性のように動作します。従って、これを動的な値に束縛するためには v-bind を使う必要があります (以下は省略構文を使ったものです):

<div v-for="item in items" :key="item.id">
  <!-- content -->
</div>

繰り返される DOM の内容が単純な場合や、性能向上のために標準の動作に意図的に頼る場合を除いて、可能なときはいつでも v-forkey を与えることが推奨されます。

これは Vue がノードを識別する汎用的な仕組みなので、key はガイドの後半でわかるように v-for に縛られない他の用途もあります。

オブジェクトや配列のような非プリミティブ値を v-for のキーとして使わないでください。代わりに、文字列や数値を使ってください。

key 属性の詳細な使い方は、key API ドキュメント を参照してください。

配列の変化を検出

変更メソッド

Vue は画面の更新もトリガするために、監視された配列の変更メソッドを抱合 (wrap) します。抱合されたメソッドは次のとおりです:

コンソールを開いて前の items 配列の例で変更メソッドを呼び出して遊んでみてください。例えば example1.items.push({ message: 'Baz' }) のようにしてみましょう。

配列の置き換え

変更メソッドは、名前が示唆するように、それらが呼ばれると元の配列を変更します。変更しないメソッドもあります。例えば、filter()concat()、そしてslice() のような、元の配列を変更しませんが、常に新しい配列を返します。変更しないメソッドで動作するとき、新しいもので古い配列を置き換えます:

example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})

これは、Vue が既存の DOM を捨てて、リスト全体を再描画の原因になると思うかもしれません。幸いにもそれはそうではありません。Vue は DOM 要素の再利用を最大化するためにいくつかのスマートなヒューリスティックを実装しているので、重複するオブジェクトを含んでいる他の配列を配列で置き換えることは、とても効率的な作業です。

注意事項

JavaScript の制限のため、Vue は配列で以下の変更を検出することはできません:

  1. インデックスでアイテムを直接設定するとき。例: vm.items[indexOfItem] = newValue
  2. 配列の長さを変更するとき。例: vm.items.length = newLength

例:

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // リアクティブではありません
vm.items.length = 2 // リアクティブではありません

上記の注意事項 1 に対処するため、以下のどちらも vm.items[indexOfItem] = newValue と同じ動作になりますが、リアクティブなシステム内で状態の更新をトリガします。

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

vm.$set インスタンスメソッドを使用することもできます。これはグローバル Vue.set のエイリアスです:

vm.$set(vm.items, indexOfItem, newValue)

上記の注意事項 2 に対処するためにも splice を使います:

vm.items.splice(newLength)

オブジェクトの変更検出の注意

再度になりますが、現代の JavaScript の制約のため、Vue はプロパティの追加や削除を検出することはできません。 例:

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` はリアクティブです

vm.b = 2
// `vm.b` はリアクティブではありません

Vue はすでに作成されたインスタンスに新しいルートレベルのリアクティブプロパティを動的に追加することはできません。しかし、Vue.set(object、key、value) メソッドを使ってネストされたオブジェクトにリアクティブなプロパティを追加することは可能です。例を挙げると:

var vm = new Vue({
  data: {
    userProfile: {
      name: 'Anika'
    }
  }
})

ネストされた userProfile オブジェクトに新しい age プロパティを追加することができます:

Vue.set(vm.userProfile, 'age', 27)

vm.$set インスタンスメソッドを使用することもできます。これはグローバル Vue.set のエイリアスです:

vm.$set(vm.userProfile, 'age', 27)

例えば Object.assign()_.extend() を使って既存のオブジェクトにいくつかの新しいプロパティを割り当てたいときがあります。このような場合は、両方のオブジェクトのプロパティを使用して新しいオブジェクトを作成する必要があります。 なので以下のやり方ではなくて:

Object.assign(vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})

新しいリアクティブプロパティをこのように追加します。

vm.userProfile = Object.assign({}, vm.userProfile, {
  age: 27,
  favoriteColor: 'Vue Green'
})

フィルタ/ソートされた結果の表示

ときどき、元のデータを実際に変更またはリセットすることなしに、フィルタリングやソートされたバージョンの配列を表示したいことがあります。このケースでは、フィルタリングやソートされた配列を返す算出プロパティを作ることができます。

例えば:

<li v-for="n in evenNumbers">{{ n }}</li>
data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
  evenNumbers: function () {
    return this.numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

算出プロパティが使えない状況ではメソッドを使うこともできます。(例: 入れ子になった v-for のループの中):

<li v-for="n in even(numbers)">{{ n }}</li>
data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
  even: function (numbers) {
    return numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

範囲付き v-for

v-for は整数値を取ることもできます。このケースでは、指定された数だけテンプレートが繰り返されます。

<div>
  <span v-for="n in 10">{{ n }} </span>
</div>

結果:

{{ n }}

<template> での v-for

テンプレート v-if と同様に、複数の要素のブロックをレンダリングするために、 v-for<template> タグを使うこともできます。例:

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

v-forv-if

v-ifv-for を同時に利用することは 推奨されません。 詳細については スタイルガイド を参照ください。

それらが同じノードに存在するとき、 v-forv-if よりも高い優先度を持ちます。これは v-if がループの各繰り返しで実行されることを意味します。以下のように、これはいくつかの項目のみのノードを描画する場合に便利です。

<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo }}
</li>

上記は、完了していない項目だけを描画します。

代わりに、ループの実行を条件付きでスキップすることを目的にしている場合は、ラッパー(wrapper) 要素 (または <template>)上で v-if に置き換えます。例えば:

<ul v-if="todos.length">
  <li v-for="todo in todos">
    {{ todo }}
  </li>
</ul>
<p v-else>No todos left!</p>

コンポーネントと v-for

このセクションでは、コンポーネントについての知識を前提としています。もし分からなければ、このセクションを遠慮なく飛ばして、理解した後に戻ってきてください。

普通の要素のように、カスタムコンポーネントで直接 v-for を使うことができます:

<my-component v-for="item in items" :key="item.id"></my-component>

2.2.0 以降で、コンポーネントで v-for を使用するとき、key は必須です

しかしながら、これはいかなるデータもコンポーネントへ自動的に渡すことはありません。なぜなら、コンポーネントはコンポーネント自身の隔離されたスコープを持っているからです。反復してデータをコンポーネントに渡すためには、プロパティを使うべきです:

<my-component
  v-for="(item, index) in items"
  v-bind:item="item"
  v-bind:index="index"
  v-bind:key="item.id"
></my-component>

自動的に item をコンポーネントに渡さない理由は、それが v-for の動作と密結合になってしまうからです。どこからデータが来たのかを明確にすることが、他の場面でコンポーネントを再利用できるようにします。

これは、単純な ToDo リストの完全な例です:

<div id="todo-list-example">
  <form v-on:submit.prevent="addNewTodo">
    <label for="new-todo">Add a todo</label>
    <input
      v-model="newTodoText"
      id="new-todo"
      placeholder="E.g. Feed the cat"
    >
    <button>Add</button>
  </form>
  <ul>
    <li
      is="todo-item"
      v-for="(todo, index) in todos"
      v-bind:key="todo.id"
      v-bind:title="todo.title"
      v-on:remove="todos.splice(index, 1)"
    ></li>
  </ul>
</div>

is="todo-item" 属性に注意してください。 これは DOM テンプレートでは必要で、なぜなら <ul> 要素の中では <li> のみが有効だからです。これは <todo-item> と同じ意味ですが、潜在的なブラウザの解析エラーを回避します。詳細は DOM テンプレート解析の注意事項 を参照してください。

Vue.component('todo-item', {
  template: '\
    <li>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </li>\
  ',
  props: ['title']
})

new Vue({
  el: '#todo-list-example',
  data: {
    newTodoText: '',
    todos: [
      {
        id: 1,
        title: 'Do the dishes',
      },
      {
        id: 2,
        title: 'Take out the trash',
      },
      {
        id: 3,
        title: 'Mow the lawn'
      }
    ],
    nextTodoId: 4
  },
  methods: {
    addNewTodo: function () {
      this.todos.push({
        id: this.nextTodoId++,
        title: this.newTodoText
      })
      this.newTodoText = ''
    }
  }
})