カテゴリ: notes
タグ: vuejs

リストレンダリング|Vue.js再学習

触れないタイミングが3ヶ月あったり忘れてしまった事に焦りを感じたので、今度は勉強したことをブロクに残していきたいと思います。 内容的には公式ドキュメントの写経のようなものになります。

今日はリストレンダリングを見ていきます。

リストレンダリング

リストレンダリング

v-forで配列の要素がある分だけ売り返し処理する

v-foritem in items形式の特別な構文を要求します。 itemsがソースデータの配列で、item繰り返されていく配列要素の*エイリアス*です。

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

結果:

  • Foo
  • Bar

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' },
    ]
  }
}) 

結果:

  • Parent - 0 - Foo
  • Parent - 1 - Bar

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

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

オブジェクトのv-for

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

結果:

  • John
  • Doe
  • 30

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

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

結果:

  • firstName: John
  • lastName: Doe
  • age: 30

indexも提供できます:

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

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

key

Vueがv-forで描画された要素のリストを更新する際、標準では「その場でパッチを適用する(in-place path)戦略が用いられます。 データのアイテムの順序が変更された場合、アイテムの順序に合わせてDOM要素を移動する代わりに、各要素にその場でパッチを適用して、その特定のインデックスに何を描画するべきかを確実に反映します。

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

これに関して、こちらのstackoverflowにデモがあって分かりやすいです。 v-bind:key を使う時と使わない時の違い

Vueが各ノードの識別情報を追跡できるヒントを与えるために、また、先程説明したような既存の要素の再利用と並び替えができるように、一意なkey属性を全てのアイテムに与える必要があります。 v-bindを使って動的な値に束縛することができます。

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

v-forkeyを指定しないと警告が出ますし、なるべく指定することが推奨されます。 これは繰り返されるDOMの内容が単純でない、または性能向上を標準の動作に意図的に頼っていない場合に限ります。

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

配列の変化を検出

変更メソッド

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

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

コンソールを開いて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は配列で以下の変更を検出することはできません。

  • インデックスでアイテムを直接指定するとき。例:vm.items[indexOfItem] = newValue
  • 配列の長さを変更するとき。例: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.protoype.splice
vm.items.splice(indexOfItem, 1, newValue)

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

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

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

vm.items.splice(newLength)

参考

Vue.set( target, key, value ) vm.$set( target, key, value ) Array.prototype.splice()

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

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

var vm = new Vue({
  data: {
    a: 1
  }
})
// 'vm.a' はリアクティブです
// (vmインスタンス作成時にプロパティが登録されていたので)

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,
  favroiteColor: 'Vue Green'
})

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

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

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

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

例えば

<li v-for="n in evenNumberse">{{ 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>
1 2 3 4 5 6 7 8 9 10

v-forで複数要素のブロックをレンダリングするには<template>を使います

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

例:

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

v-forv-if

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

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

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

代わりに、ループの実行を条件付きでスキップすることをも目的にしている場合は、ラッパー要素または<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を使うことができます。 ※2.2.0以降はコンポーネントでv-forを使用するとき、keyは必須です。

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

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

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

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

下記は、単純なToDoリストの完全な例です。

<div id="todo-list-example">
  <input
    v-model="newTodoText"
    @keyup.enter="addNewTodo"
    placeholder="Add a todo"
  >
  <ul>
    <li
      is="todo-item"
      v-for="(todo, index) in todos"
      :key="todo.id"
      :title="todo.title"
      @remove="todos.splice(index, 1)"
    ></li>
  </ul>
</div>

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

About

はらぺこ

職業:フロントエンドエンジニア
趣味:自転車/テニス/空手
愛車:
 ✓ブリヂストン オルディナs5
 ✓アンカー RS8 EL

神戸にて爆誕。
友人から「お前がマスターして教えてくれ」と渡された『HTML&スタイルシート ポケットリファレンス』がキッカケでWeb業界への一歩を踏み出す事になる。

2013年7月にクロスバイクを購入して自転車にドハマリする。
全力で遊ぶ合間に、ほどほどに頑張る日々を過ごしています。

飼い主 はっぴー

2014年10月20日(誕生日)の引っ越し日、捨て猫になっていた所、運命の出会いを果たす。 
(その日は雨で、3日間雨が続く寒い日だった。このまま放っておけないと自転車生活と秤にかけて、はっぴーを選択する)
5時間かけて油断させ、近づいてきた所をガッと拾い上げ、そのまま家へ猛ダッシュ、玄関の鍵を閉めて家族になる。以後オレたちはファミリーだ。

助けたつもりが、実際は自分の方が精神的に救われている。はっぴーなしでは生きていけない。(最早「飼い主」と言って差し支えないだろう)

「はっぴー」という名前の由来は、『幸せになってほしい』という想いで名付けた。