Vue-101-Day 9

|

使用v-for 這個 directive 將 Array/Object 裡的內容對應成 HTML 元素

對 Array 使用 v-for

<div id="app-10">
  <h1 v-for="item in items">{{ item.message }}</h1>
</div>
var vm10 = new Vue({
  el: '#app-10',
  data: {
    items: [{ message: 'Hello' }, { message: 'Hola' }]
  }
})

有時候可能會需要知道目前 index 到哪裡,Vue 在第二個參數(可選填)上,提供目前的 index

<div id="app-10">
   <h1 v-for="(item, index) in items">{{ index }} - {{ item.message }}</h1>
</div>

除了 in 的用法,也可以使用 of 的寫法達成同樣效果

<div id="app-10">
  <h1 v-for="(item, index) of items">{{ index }} - {{ item.message }}</h1>
</div>

對 Object 使用 v-for

<div id="app-11">
  <h1 v-for="value of object">{{ value }}</h1>
</div>
var vm11 = new Vue({
  el: '#app-11',
  data: {
    object: {
      title: 'coding diary',
      author: 'David',
      publishedAt: '2019-01-01'
    }
  }
})

結果

coding diary
David
2019-01-01

在 Object 使用也可以選擇性參數,不過順序及意義會不太一樣。

以下範例來看

  • 第一個(即 name):object 的屬性名稱
  • 第二個 (即 index):目前項目 index
<div id="app-11">
  <h1 v-for="(value, name, index) of object">
    {{ index }} - {{ name }} - {{ value }}
  </h1>
</div>

結果

0 - title - coding diary
1 - author - David
2 - publishedAt - 2019-01-01

維護狀態

Vue 渲染更新的 List 元素,預設會重複使用已經渲染好的元素

<div id="app-12">
  <h1 v-for="(item, index) of items">
    <input type="text" :placeholder="item.name" />
  </h1>
</div>
var vm12 = new Vue({
  el: '#app-12',
  data: {
    items: [
      { id: '1', name: '選項 1' },
      { id: '2', name: '選項 2' },
      { id: '3', name: '選項 3' },
      { id: '4', name: '選項 4' }
    ]
  }
})

當改變元素順序時,會發現 input 已經填寫的內容並沒有跟著改變 view view

這時候可以加上 key確保每個元素都是唯一值,讓元素更新時可以重複渲染

<div id="app-12">
  <h1 v-for="(item, index) of items">
    <input type="text" :placeholder="item.name" :key="item.id" />
  </h1>
</div>

Mutation Methods

以下方法會觸發 View 的更新

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

更新 Array 資料範例

vm12.items = vm12.items.filter(el => el.id % 2 === 0)

注意事項

直接以下方法,並不會觸發 View 上的更新

  1. 直接設定 index 上的值, e.g:vm12.items[index] = ‘Hi’
  2. 更新 array 的長度, e.g:vm12.items.length = 1

範例:

<div id="app-13">
   <h1 v-for="(item, index) of items">{{ index }} - {{ item }}</h1> 
</div>
var vm13 = new Vue({
  el: '#app-13',
  data: {
    items: [1, 2, 3, 4]
  }
})

這時候要把值更新到 View 上可以使用 Vue.set,來達成目的

Vue.set(vm13.items, 0, 'Hi~~~~~')

假使 length 只有 4,直接設定Vue.set(vm13.items, 5, 'Hi~~~~~'),vm13.items[4]值會為 undefined

0 - 1
1 - 2
2 - 3
3 - 4
4 -
5 - Hi~~~~~

註:也可以使用vm.$set,它是 Vue.set 的 global alias

在 Object 也是同樣道理,設定vm14.profile.age = 18,也並不會更新到畫面,使用Vue.set(object, propertyName, value)將值更新到畫面

<div id="app-14">
  <h1 v-for="(value, name, index) in profile">{{ name }} - {{ value }}</h1>
</div>
Vue.set(vm14.profile, 'age', 18)

但要同時設定多個屬性值,使用 Vue.set 就顯得不方便,要寫很多程式碼,這時候可以使用Object.assign()達成同時設定多個屬性值

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

顯示過濾條件及排序後的結果

以下範例中,要顯示偶數

<div id="app-13">
  <h1 v-for="number of evenNumbers">{{ number }}</h1>
</div>
var vm13 = new Vue({
  el: '#app-13',
  data: {
    numbers: [1, 2, 3, 4, 5]
  },
  computed: {
    evenNumbers: function() {
      return this.numbers.filter(function(el) {
        return el % 2 === 0
      })
    }
  }
})

但有些時候,使用 computed 方法是不可行的 (e.g. inside nested v-for loops),此時可以使用 methods代替

<div id="app-13">
  <h1 v-for="number of evenNumbers(numbers)">{{ number }}</h1>
</div>
var vm13 = new Vue({
  el: '#app-13',
  data: {
    numbers: [1, 2, 3, 4, 5]
  },
  methods: {
    evenNumbers: function() {
      return this.numbers.filter(function(el) {
        return el % 2 === 0
      })
    }
  }
})

使用 v-for 建立一個範圍內的值

<div id="app-15">
  <span v-for="n in 10"> {{ n }}</span>
</div>
1 2 3 4 5 6 7 8 9 10

在 template 使用 v-for

<div id="app-15">
  <template v-for="n in 10">
    <li>{{ n }}</li>
  </template>
</div>

同時使用 v-for 及 v-if

在同一個 element 上,v-for的優先權 大於 v-if,也就是 loop 跑 10 次,v-if 也會跑 10 次

<div id="app-16">
  <li v-for="todo in todos" v-if="todo.isCompleted">
    Completed: {{ todo.task }}
  </li>
</div>
var vm16 = new Vue({
  el: '#app-16',
  data: {
    todos: [
      { task: 'do homework', isCompleted: true },
      { task: 'do chores', isCompleted: false }
    ]
  }
})

特殊情況

若同時出現 v-for、v-if、v-else,且陣列內容長度為 0

範例程式

<div id="app-16">
  <li v-for="todo in todos" v-if="todo.length !== 0">
    Completed: {{ todo.task }}
  </li>
  <li v-else>nothing to show</li>
</div>

預期結果應該是 nothing to show,可是輸出結果卻沒有該內容,因為v-for 優先權大於 v-if,當為空陣列,v-for迭代沒有資料,也就不會執行v-if

解決方法

v-if 額外拉出來判斷長度為 0 時的結果

<div id="app-16">
  <li v-for="todo in todos">Completed: {{ todo.task }}</li>
  <li v-if="todos.length === 0">nothing to show</li>
</div>

參考資料:

Comments