清單渲染


目錄


在元件上使用v-for


v-for 把一個陣列對應到一組元素我們可以用v-for 指令是基於一個陣列來渲染一個清單。 v-for

指令需要使用

item in items

形式的特殊語法,其中

items1.jpg 是來源資料數組,而

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' }
    ]
  }
})
結果:

2.jpg

v-for

區塊中,我們可以存取所有父作用域的屬性。 v-for 也支援一個可選的第二個參數,即目前項目的索引。

<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' }
    ]
  }
})
結果:


你也可以用of 取代in 作為分隔符,因為它更接近JavaScript 迭代器的語法:
<div v-for="item of items"></div>


#在

v-for

裡使用物件

3.jpg

你也可以用

v-for

來遍歷一個物件的屬性。 4.jpg

<ul id="v-for-object" class="demo">
  <li v-for="value in object">
    {{ value }}
  </li>
</ul>
new Vue({
  el: '#v-for-object',
  data: {
    object: {
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    }
  }
})

結果:###############你也可以提供第二個的參數為property 名稱(也就是鍵名):###
<div v-for="(value, name) in object">
  {{ name }}: {{ value }}
</div>
#### ######

也可以用第三個參數作為索引:

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

5.jpg

#在遍歷物件時,會按 Object.keys()的結果遍歷,但是不能保證它的結果在不同的 JavaScript 引擎下都一致。


維護狀態


#當Vue 正在更新使用v -for 渲染的元素清單時,它預設使用「就地更新」的策略。如果資料項的順序被改變,Vue 將不會移動 DOM 元素來匹配資料項的順序,而是就地更新每個元素,並確保它們在每個索引位置正確渲染。這個類似 Vue 1.x 的 track-by="$index"

這個預設的模式是高效率的,但是只適用於不依賴子元件狀態或臨時 DOM 狀態 (例如:表單輸入值) 的清單渲染輸出

為了給Vue 一個提示,以便它能追蹤每個節點的身份,從而重複使用和重新排序現有元素,你需要為每項提供一個唯一key 屬性:

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

建議盡可能在使用v-for 時提供key attribute,除非遍歷輸出的DOM 內容非常簡單,或者是刻意依賴預設行為以獲取效能上的提升。

因為它是 Vue 辨識節點的一個通用機制,key 並且不僅與 v-for 特別關聯。後面我們將在指南中看到,它還具有其它用途。

不要使用物件或陣列之類的非基本型別值作為 v-for 的 key。請用字串或數值類型的值。

更多 key attribute 的細節用法請移步至 key 的 API 文件


陣列更新偵測



  • 變異方法(mutation method)

  • Vue 將被偵聽的陣列的變異方法進行了包裹,所以它們也會觸發視圖更新。這些被包裹過的方法包括:

  • push()
  • ##shift()

  • #unshift()

  • ##splice()

  • sort()

reverse ()

你可以開啟控制台,然後對前面範例的
items

陣列嘗試呼叫變異方法。如

example1.items.push({ message: 'Baz' })

#########取代陣列##########

變異方法,顧名思義,會改變呼叫了這些方法的原始陣列。相較之下,也有非變異 (non-mutating method) 方法,例如 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 // 不是响应性的

為了解決第一類問題,以下兩種方式都可以實現和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)

為了解決第二類問題,你可以使用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, propertyName, value) 方法來為嵌套物件新增響應式屬性。例如,對於:

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

你可以新增一個新的age 屬性到巢狀的userProfile 物件:

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>

結果:

1.jpg


#在<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-for 與v-if 一同使用


# #注意我們

建議在同一元素上使用 v-if 和 v-for。更多細節可參考 風格指南

當它們處於相同節點,
v-for

的優先權比v-if 更高,這表示v-if 將分別重複運行於每個v-for 迴圈中。當你只想為部分項目渲染節點時,這個優先權的機制會十分有用,如下:

<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo }}
</li>
上面的程式碼只會渲染未完成的 todo。

而如果你的目的是有條件地跳過迴圈的執行,那麼可以將

v-if

置於外層元素(或<template> )上。如:

<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 現在是必須的。

然而,任何資料都不會被自動傳遞到元件裡,因為元件有自己獨立的作用域。為了把迭代資料傳遞到元件裡,我們要使用prop:

<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 = ''
    }
  }
})

1.gif