直接跳到內容

列表渲染

v-for

我們可以使用 v-for 指令基於一個數組來渲染一個列表。v-for 指令的值需要使用 item in items 形式的特殊語法,其中 items 是源數據的數組,而 item 是迭代項的別名

js
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
js
data() {
  return {
    items: [{ message: 'Foo' }, { message: 'Bar' }]
  }
}
template
<li v-for="item in items">
  {{ item.message }}
</li>

v-for 塊中可以完整地訪問父作用域內的屬性和變量。v-for 也支持使用可選的第二個參數表示當前項的位置索引。

js
const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
js
data() {
  return {
    parentMessage: 'Parent',
    items: [{ message: 'Foo' }, { message: 'Bar' }]
  }
}
template
<li v-for="(item, index) in items">
  {{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
  • Parent - 0 - Foo
  • Parent - 1 - Bar
  • v-for 變量的作用域和下面的 JavaScript 代碼很類似:

    js
    const parentMessage = 'Parent'
    const items = [
      /* ... */
    ]
    
    items.forEach((item, index) => {
      // 可以訪問外層的 `parentMessage`
      // 而 `item` 和 `index` 只在這個作用域可用
      console.log(parentMessage, item.message, index)
    })

    注意 v-for 是如何對應 forEach 回調的函數簽名的。實際上,你也可以在定義 v-for 的變量別名時使用解構,和解構函數參數類似:

    template
    <li v-for="{ message } in items">
      {{ message }}
    </li>
    
    <!-- 有 index 索引時 -->
    <li v-for="({ message }, index) in items">
      {{ message }} {{ index }}
    </li>

    對於多層嵌套的 v-for,作用域的工作方式和函數的作用域很類似。每個 v-for 作用域都可以訪問到父級作用域:

    template
    <li v-for="item in items">
      <span v-for="childItem in item.children">
        {{ item.message }} {{ childItem }}
      </span>
    </li>

    你也可以使用 of 作為分隔符來替代 in,這更接近 JavaScript 的迭代器語法:

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

    v-for 與對象

    你也可以使用 v-for 來遍歷一個對象的所有屬性。遍歷的順序會基於對該對象調用 Object.keys() 的返回值來決定。

    js
    const myObject = reactive({
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    })
    js
    data() {
      return {
        myObject: {
          title: 'How to do lists in Vue',
          author: 'Jane Doe',
          publishedAt: '2016-04-10'
        }
      }
    }
    template
    <ul>
      <li v-for="value in myObject">
        {{ value }}
      </li>
    </ul>

    可以通過提供第二個參數表示屬性名 (例如 key):

    template
    <li v-for="(value, key) in myObject">
      {{ key }}: {{ value }}
    </li>

    第三個參數表示位置索引:

    template
    <li v-for="(value, key, index) in myObject">
      {{ index }}. {{ key }}: {{ value }}
    </li>

    v-for 裡使用範圍值

    v-for 可以直接接受一個整數值。在這種用例中,會將該模板基於 1...n 的取值範圍重複多次。

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

    注意此處 n 的初值是從 1 開始而非 0

    <template> 上的 v-for

    與模板上的 v-if 類似,你也可以在 <template> 標籤上使用 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-ifv-for 的優先級更高。這意味著 v-if 的條件將無法訪問到 v-for 作用域內定義的變量別名:

    template
    <!--
     這會拋出一個錯誤,因為屬性 todo 此時
     沒有在該實例上定義
    -->
    <li v-for="todo in todos" v-if="!todo.isComplete">
      {{ todo.name }}
    </li>

    在外先封装一層 <template> 再在其上使用 v-for 可以解決這個問題 (這也更加明顯易讀):

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

    通過 key 管理狀態

    Vue 默認按照“就地更新”的策略來更新通過 v-for 渲染的元素列表。當數據項的順序改變時,Vue 不會隨之移動 DOM 元素的順序,而是就地更新每個元素,確保它們在原本指定的索引位置上渲染。

    默認模式是高效的,但只適用於列表渲染輸出的結果不依賴子組件狀態或者臨時 DOM 狀態 (例如表單輸入值) 的情況

    為了給 Vue 一個提示,以便它可以跟蹤每個節點的標識,從而重用和重新排序現有的元素,你需要為每個元素對應的塊提供一個唯一的 key attribute:

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

    當你使用 <template v-for> 時,key 應該被放置在這個 <template> 容器上:

    template
    <template v-for="todo in todos" :key="todo.name">
      <li>{{ todo.name }}</li>
    </template>

    注意

    key 在這裡是一個通過 v-bind 綁定的特殊 attribute。請不要和v-for 中使用對象裡所提到的對象屬性名相混淆。

    推薦在任何可行的時候為 v-for 提供一個 key attribute,除非所迭代的 DOM 內容非常簡單 (例如:不包含組件或有狀態的 DOM 元素),或者你想有意採用默認行為來提高性能。

    key 綁定的值期望是一個基礎類型的值,例如字符串或 number 類型。不要用對象作為 v-for 的 key。關於 key attribute 的更多用途細節,請參閱 key API 文檔

    組件上使用 v-for

    這一小節假設你已了解組件的相關知識,或者你也可以先跳過這裡,之後再回來看。

    我們可以直接在組件上使用 v-for,和在一般的元素上使用沒有區別 (別忘記提供一個 key):

    template
    <MyComponent v-for="item in items" :key="item.id" />

    但是,這不會自動將任何數據傳遞給組件,因為組件有自己獨立的作用域。為了將迭代後的數據傳遞到組件中,我們還需要傳遞 props:

    template
    <MyComponent
      v-for="(item, index) in items"
      :item="item"
      :index="index"
      :key="item.id"
    />

    不自動將 item 注入組件的原因是,這會使組件與 v-for 的工作方式緊密耦合。明確其數據的來源可以使組件在其他情況下重用。

    這裡是一個簡單的 Todo List 的例子,展示了如何通過 v-for 來渲染一個組件列表,並向每個實例中傳入不同的數據。

    這裡是一個簡單的 Todo List 的例子,展示了如何通過 v-for 來渲染一個組件列表,並向每個實例中傳入不同的數據。

    數組變化偵測

    變更方法

    Vue 能夠偵聽響應式數組的變更方法,並在它們被調用時觸發相關的更新。這些變更方法包括:

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

    替換一個數組

    變更方法,顧名思義,就是會對調用它們的原數組進行變更。相對地,也有一些不可變 (immutable) 方法,例如 filter()concat()slice(),這些都不會更改原數組,而總是返回一個新數組。當遇到的是非變更方法時,我們需要將舊的數組替換為新的:

    js
    // `items` 是一個數組的 ref
    items.value = items.value.filter((item) => item.message.match(/Foo/))
    js
    this.items = this.items.filter((item) => item.message.match(/Foo/))

    你可能認為這將導致 Vue 丟棄現有的 DOM 並重新渲染整個列表——幸運的是,情況並非如此。Vue 實現了一些巧妙的方法來最大化對 DOM 元素的重用,因此用另一個包含部分重疊對象的數組來做替換,仍會是一種非常高效的操作。

    展示過濾或排序後的結果

    有時,我們希望顯示數組經過過濾或排序後的內容,而不實際變更或重置原始數據。在這種情況下,你可以創建返回已過濾或已排序數組的計算屬性。

    舉例來說:

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

    在計算屬性不可行的情況下 (例如在多層嵌套的 v-for 循環中),你可以使用以下方法:

    js
    const sets = ref([
      [1, 2, 3, 4, 5],
      [6, 7, 8, 9, 10]
    ])
    
    function even(numbers) {
      return numbers.filter((number) => number % 2 === 0)
    }
    js
    data() {
      return {
        sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
      }
    },
    methods: {
      even(numbers) {
        return numbers.filter(number => number % 2 === 0)
      }
    }
    template
    <ul v-for="numbers in sets">
      <li v-for="n in even(numbers)">{{ n }}</li>
    </ul>

    在計算屬性中使用 reverse()sort() 的時候務必小心!這兩個方法將變更原始數組,計算函數中不應該這麼做。請在調用這些方法之前創建一個原數組的副本:

    diff
    - return numbers.reverse()
    + return [...numbers].reverse()
    列表渲染已經加載完畢