列表渲染 v-for

v-for 指令基于一个数组来渲染一个列表。它是 Vue 中最常用的指令之一,用于展示动态数据。

基本用法

遍历数组

<ul id="app">
  <li v-for="item in items">
    {{ item.text }}
  </li>
</ul>

<script>
new Vue({
  el: '#app',
  data: {
    items: [
      { text: '学习 JavaScript' },
      { text: '学习 Vue' },
      { text: '做个项目' }
    ]
  }
})
</script>

获取索引

<ul>
  <li v-for="(item, index) in items">
    {{ index }} - {{ item.text }}
  </li>
</ul>

遍历对象

<ul id="app">
  <li v-for="value in object">
    {{ value }}
  </li>
</ul>

<script>
new Vue({
  el: '#app',
  data: {
    object: {
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    }
  }
})
</script>

获取键名和索引

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

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

遍历整数

v-for 也可以遍历整数:

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

输出:1 2 3 4 5 6 7 8 9 10

在 template 上使用

v-if 类似,可以使用 <template> 渲染多个元素:

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

数组更新检测

变异方法

Vue 对以下数组方法进行了包装,调用它们会触发视图更新:

方法说明
push()末尾添加
pop()末尾删除
shift()开头删除
unshift()开头添加
splice()删除/插入
sort()排序
reverse()反转
// 这些方法会触发更新
this.items.push({ text: '新项目' })
this.items.pop()
this.items.splice(0, 1)

替换数组

非变异方法(如 filterconcatslice)不会改变原数组,而是返回新数组:

// 需要替换原数组
this.items = this.items.filter(function(item) {
  return item.isActive
})

注意事项

Vue 无法检测以下变化

// ❌ 通过索引直接设置项
this.items[index] = newValue

// ✅ 使用 Vue.set 或 $set
Vue.set(this.items, index, newValue)
this.$set(this.items, index, newValue)

// ❌ 修改数组长度
this.items.length = newLength

// ✅ 使用 splice
this.items.splice(newLength)

对象变更检测

对于对象,Vue 也有限制:

// ❌ 添加新属性,Vue 无法检测
this.user.newProp = 'value'

// ✅ 使用 Vue.set
Vue.set(this.user, 'newProp', 'value')

// ✅ 使用 Object.assign 创建新对象
this.user = Object.assign({}, this.user, { newProp: 'value' })

显示过滤/排序结果

使用计算属性:

<li v-for="n in evenNumbers">{{ n }}</li>

<script>
new Vue({
  data: {
    numbers: [1, 2, 3, 4, 5]
  },
  computed: {
    evenNumbers: function() {
      return this.numbers.filter(function(number) {
        return number % 2 === 0
      })
    }
  }
})
</script>

或者使用方法(需要传参时):

<ul>
  <li v-for="n in even(numbers)">{{ n }}</li>
</ul>

<script>
new Vue({
  data: {
    numbers: [1, 2, 3, 4, 5]
  },
  methods: {
    even: function(numbers) {
      return numbers.filter(function(number) {
        return number % 2 === 0
      })
    }
  }
})
</script>

实际应用场景

1. 表格渲染

<table>
  <thead>
    <tr>
      <th>姓名</th>
      <th>年龄</th>
      <th>操作</th>
    </tr>
  </thead>
  <tbody>
    <tr v-for="(user, index) in users" :key="user.id">
      <td>{{ user.name }}</td>
      <td>{{ user.age }}</td>
      <td>
        <button @click="edit(index)">编辑</button>
        <button @click="remove(index)">删除</button>
      </td>
    </tr>
  </tbody>
</table>

2. 嵌套列表

<div v-for="category in categories" :key="category.id">
  <h3>{{ category.name }}</h3>
  <ul>
    <li v-for="item in category.items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</div>

3. 带状态的列表

<div v-for="todo in todos" :key="todo.id">
  <input type="checkbox" v-model="todo.done">
  <span :class="{ done: todo.done }">{{ todo.text }}</span>
</div>

<style>
.done {
  text-decoration: line-through;
  color: #999;
}
</style>

4. 分页列表

<div v-for="item in paginatedItems" :key="item.id">
  {{ item.name }}
</div>

<button @click="prevPage" :disabled="currentPage === 1">上一页</button>
<span>第 {{ currentPage }} 页</span>
<button @click="nextPage" :disabled="currentPage === totalPages">下一页</button>

<script>
new Vue({
  computed: {
    paginatedItems: function() {
      var start = (this.currentPage - 1) * this.pageSize
      var end = start + this.pageSize
      return this.items.slice(start, end)
    },
    totalPages: function() {
      return Math.ceil(this.items.length / this.pageSize)
    }
  }
})
</script>

v-for 与 v-if

不要在同一元素上使用 v-ifv-for

<!-- ❌ 不推荐 -->
<li v-for="user in users" v-if="user.isActive">
  {{ user.name }}
</li>

<!-- ✅ 推荐:使用计算属性 -->
<li v-for="user in activeUsers" :key="user.id">
  {{ user.name }}
</li>

<script>
computed: {
  activeUsers: function() {
    return this.users.filter(function(user) {
      return user.isActive
    })
  }
}
</script>

如果必须使用,将 v-for 放在外层:

<template v-for="user in users" :key="user.id">
  <li v-if="user.isActive">
    {{ user.name }}
  </li>
</template>

小结

用法说明
v-for="item in items"遍历数组
v-for="(item, index) in items"带索引
v-for="(value, key) in object"遍历对象
v-for="n in 10"遍历整数

记住:

  • 始终为 v-for 添加 :key
  • 使用变异方法或 $set 更新数组
  • 避免在同一元素上使用 v-ifv-for