前言

這篇主要介紹 v-forv-ifv-show 是甚麼,以及如何使用。


v-ifv-show

v-if

v-if 這個指令可以把它想成是元素的是 if 語法,當 v-if 的值等於 true 時,才會渲染,否則元素不會出現在網頁上,並且 v-if 也支援 JavaScript 的表達式。Vue 也提供了 v-elsev-else-if,作用是甚麼應該不用我多說了:

<div id="app">
  <h1 v-if="who === 'tom'">Hello Tom!</h1>
  <h1 v-else-if="who === 'lucy'">Hello Lucy!</h1>
  <h1 v-else>Hello Brah!</h1>
</div>
const app = Vue.createApp({
  data() {
    return {
      who: 'Lucy' 
    }
  }
}).mount('#app')

渲染結果為:

<div id="app">
  <h1>Hello Lucy!</h1>
</div>

v-show

v-show 用法與 v-if 大致一樣,不同的地方是 v-show 一定會被保留在 DOM,它只是簡單的切換元素的 CSS 屬性 display,而 v-if 則是如果傳入的值是 false,那他就不會存在 DOM 裡:

<div id="app">
  <!-- 畫面不會顯示,並且不會存在於 DOM -->
  <h1 v-if="isShow">Hello Vue3!</h1>
  <!-- 畫面不會顯示,但會存在於 DOM  -->
  <p v-if="isShow">hello Vue2</p>
</div>
const app = Vue.createApp({
  data() {
    return {
      isShow: false 
    }
  }
}).mount('#app')

v-show

Tips

  1. v-show 如果不為 true,則對此元素設置 display: none,並且還在 DOM 裡
  2. v-if 如果不為 true,則不會出現在 DOM 裡

v-for

v-for 使用陣列

透過 v-for,我們可以減少重複使用的元素。讓 HTML 結構更明瞭。v-for 要使用 item in items 此種語法。items 就是你傳入的陣列,item 則是被迭代的陣列元素的別名,所以不一定要取名為 item

<div id="app">
  <ul>
    <li v-for="person in persons">
      {{ person.name }} - {{ person.age }}
    </li>
  </ul>
</div>
const app = Vue.createApp({
  data() {
    return {
      persons: [
        { name: 'Tom', age: 22 },
        { name: 'lucy', age: 20}
      ] 
    }
  }
}).mount('#app')

渲染結果為:

<div id="app">
  <ul>
    <li>Tom - 22</li>
    <li>lucy - 20</li>
  </ul>
</div>

v-for 提供了第二個參數 index 參數,就是當前的索引:

<div id="app">
  <ul>
    <li v-for="(person, index) in persons">
      {{ person.name }} - {{ index }} - {{ person.age }}
    </li>
  </ul>
</div>

渲染結果為:

<div id="app">
  <ul>
    <li>Tom - 0 - 22</li>
    <li>lucy - 1 - 20</li>
  </ul>
</div>

Tips
除了使用 in,還可以使用 of 語法:<div v-for="item of items"></div>

v-for 使用物件

你也可以傳入一個物件到 v-for 來遍歷物件的所有屬性:

<div id="app">
  <div v-for="value in objects">
    {{ value }}
  </div>
</div>
const app = Vue.createApp({
  data() {
    return {
      objects: {
        name: 'tom',
        age: 20,
        job: 'Frontend Developer'
      }
    }
  }
}).mount('#app')

渲染結果為:

<div id="app">
  <div>tom</div>
  <div>20</div>
  <div>Frontend Developer</div>
</div>

v-for 物件形式可傳入 3 個參數,記得一定要按照順序:

  1. 屬性值 value
  2. 屬性名稱 name
  3. 索引 index
<div id="app">
  <div v-for="(value, name, index) in objects">
    {{ name }} - {{ value }} - {{ index }}
  </div>
</div>
const app = Vue.createApp({
  data() {
    return {
      objects: {
        name: 'tom',
        age: 20,
        job: 'Frontend Developer'
      }
    }
  }
}).mount('#app')

渲染結果為:

<div id="app">
  <div>name - tom - 0</div>
  <div>age - 20 - 1</div>
  <div>job - Frontend Developer - 2</div>
</div>

v-for 使用 key

v-for 更新已渲染的元素列表時,預設使用 “就地複用” 策略,當列表資料修改時,會根據 key 值去判斷某個值是否修改,如果修改,則重新渲染,否則複用之前的元素。而我們在使用時經常會用 v-forindex 來當作 key,但其實非常不推薦這樣做:

假設有個 list 陣列:

<div id="app">
  <div v-for="(item, index) in list" :key="index">
  </div>
</div>
const app = Vue.createApp({
  data() {
    return {
      list: [
        { id: 1, name: 'test1' },
        { id: 2, name: 'test2' },
        { id: 3, name: 'test3' }
      ]
    }
  }
}).mount('#app')

如果在最後加入一個新資料:

const app = Vue.createApp({
  data() {
    return {
      list: [
        { id: 1, name: 'test1' },
        { id: 2, name: 'test2' },
        { id: 3, name: 'test3' },
        { id: 4, name: '這筆資料懂得甚麼叫排隊' },
      ]
    }
  }
}).mount('#app')

這時前三筆資料會複用之前的元素,這種時候用 index 作為 key 沒啥問題。

但如果是在中間插入新資料:

const app = Vue.createApp({
  data() {
    return {
      list: [
        { id: 1, name: 'test1' },
        { id: 4, name: '插隊的資料' },
        { id: 2, name: 'test2' },
        { id: 3, name: 'test3' },
      ]
    }
  }
}).mount('#app')

此時更新渲染資料會發現:

原先的資料:              插入後的資料:
key: 1, index: 0 name: test1    key: 0, index: 0, name: test1
key: 2, index: 1 name: test2    key: 1, index: 1, name: 這筆資料在插隊
key: 3, index: 2 name: test3    key: 2, index: 2, name: test2
                                key: 3, index: 3, name: test3

觀察上面會發現除了地一筆資料,其他三筆都被重新渲染了,這就是使用 indexkey 的弊端,我明明只要修改一項資料,卻要重新渲染其他筆資料。

這裡解決的辦法就是使用 list 裡的不會變動並且唯一的值 id 來當作 key

原先的資料:                      插入後的資料:
key: 1, id: 1, index: 0 name: test1    key: 1, id: 1,  index: 0, name: test1
key: 2, id: 2, index: 1 name: test2    key: 4, id: 4,  index: 1, name: 這筆資料在插隊
key: 3, id: 3, index: 2 name: test3    key: 2, id: 2,  index: 2, name: test2
                                       key: 3, id: 3,  index: 3, name: test3

現在插入後的資料只有一筆資料做了變化,所以只需要新渲染這筆資料就好,其他資料會複用之前的資料。

懶人包
key 請綁定唯一並且不會變動的值,避免因更動一筆資料導致其他資料都需要重新渲染

v-for 不要和 v-if 一起使用

當這兩個指令在同個標籤時, v-if 的優先性會比 v-for 還高,這表示 v-if 無法使用 v-for 裡的變數:

<div id="app">
  <!-- Cannot read properties of undefined (reading 'isDone') -->
  <div v-for="todo in todos" v-if="!todo.isDone">
    {{ todo.name }}
  </div>
</div>
const app = Vue.createApp({
  data() {
    return {
      todos: [
        { name: 'learn Vue3', isDone: true },
        { name: 'learn JavaScript', isDone: false },
      ]
    }
  }
}).mount('#app')

如果不想多用一個元素包起來,可以使用 <template> 標籤,它不會被渲染到 DOM 上:

<div id="app">
  <template v-for="todo in todos">
    <div v-if="!todo.isDone">
      {{ todo.name }}
    </div>
  <template/>
</div>

參考

Vue 官網
掘金