源本科技 | 码上会

Vue3 列表渲染

2026/04/22
24
0

引言

Vue3 的列表渲染通过 v-for 指令实现,基于数组或可迭代对象生成重复元素。推荐使用动态唯一键值以优化组件更新和避免不必要的 DOM 操作。数组变化时,Vue3 能智能地最小化变更 DOM。列表中的响应式对象内部属性改变也会触发视图更新。

v-for

我们可以使用 v-for 指令基于一个数组来渲染一个列表。v-for 指令的值需要使用 item in items 形式的特殊语法,其中 items 是源数据的数组,而 item 是迭代项的别名,同时也支持获取索引值 index

基础语法

// 响应式用户列表数据源
const userList = ref([{ id: 1, account: 'admin1' }, { id: 2, account: 'admin2' }])
<li v-for="item in userList" :key="item.id">
  {{ item.account }}
</li>

代码示例

<template>
  <div class="user-box">
    <h3>用户列表</h3>
    <ul>
      <li v-for="(item, index) in userList" :key="item.id">
        索引:{{ index }} | 用户 ID:{{ item.id }} | 账号:{{ item.account }}
      </li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'

// 定义用户类型接口
interface User {
  id: number
  account: string
}

// 统一维护一份响应式用户数组
const userList = ref<User[]>([
  { id: 1, account: 'admin1' },
  { id: 2, account: 'admin2' }
])
</script>

<style scoped>
.user-box { padding: 16px; }
li { margin: 8px 0; }
</style>
  • 定义响应式数据

    • 使用 ref 创建响应式数组 userList,搭配 TS 接口规范数据类型

    • 直接初始化用户数据,结构统一、便于后续扩展

  • 列表渲染

    • 使用 v-for 遍历数组,同时获取迭代项与索引

    • 绑定数据唯一 id 作为 key,保证列表更新精准高效

template

与模板上的 v-if 类似,你也可以在 <template> 标签上使用 v-for 渲染包含多个元素的块,<template> 不会被渲染为真实 DOM,适合批量渲染一组关联元素。

<template>
  <div class="user-box">
    <h3>用户详情列表</h3>
    <ul>
      <!-- 延续同一数据源,批量渲染用户信息组 -->
      <template v-for="item in userList" :key="item.id">
        <li>用户 ID:{{ item.id }}</li>
        <li>用户账号:{{ item.account }}</li>
        <li class="line">——————————</li>
      </template>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
interface User {
  id: number
  account: string
}
// 沿用同一份用户数据源
const userList = ref<User[]>([
  { id: 1, account: 'admin1' },
  { id: 2, account: 'admin2' }
])
</script>

<style scoped>
.user-box { padding: 16px; }
li { margin: 4px 0; }
.line { list-style: none; color: #ccc; }
</style>

v-for 与 v-if

同时使用 v-ifv-for 是不推荐的,Vue3 中 v-for 优先级高于 v-if,同节点使用会造成性能浪费且逻辑混乱。

错误的做法

<template>
  <ul>
    <!-- 同节点使用 v-for 与 v-if,优先级混乱、性能差 -->
    <li v-for="todo in todoList" v-if="!todo.isComplete" :key="todo.id">
      {{ todo.content }}
    </li>
  </ul>
</template>

<script setup lang="ts">
import { ref } from 'vue'
interface Todo {
  id: number
  content: string
  isComplete: boolean
}
// 统一待办数据源
const todoList = ref<Todo[]>([
  { id: 1, content: '完成 Vue 文档编写', isComplete: false },
  { id: 2, content: '整理列表渲染案例', isComplete: true }
])
</script>

正确的做法

在外层包装 <template> 执行遍历,内部节点使用 v-if 做条件判断,逻辑清晰且符合规范。

<template>
  <div class="todo-box">
    <h3>未完成任务</h3>
    <ul>
      <!-- 沿用同一份待办数据,逻辑连贯 -->
      <template v-for="todo in todoList" :key="todo.id">
        <li v-if="!todo.isComplete">{{ todo.content }}</li>
      </template>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
interface Todo {
  id: number
  content: string
  isComplete: boolean
}

const todoList = ref<Todo[]>([
  { id: 1, content: '完成 Vue 文档编写', isComplete: false },
  { id: 2, content: '整理列表渲染案例', isComplete: true }
])
</script>
</script>

<style scoped>
.todo-box { padding: 16px; }
</style>

管理状态

Vue 默认按照“就地更新”策略更新 v-for 渲染的元素列表,为跟踪节点标识、重用与排序元素,必须为每个项提供唯一 key 属性。

<template>
  <div class="user-box">
    <!-- 完全沿用前文用户数据源,key 绑定唯一 id -->
    <div v-for="item in userList" :key="item.id" class="user-item">
      用户 ID:{{ item.id }},账号:{{ item.account }}
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
interface User {
  id: number
  account: string
}

const userList = ref<User[]>([
  { id: 1, account: 'admin1' },
  { id: 2, account: 'admin2' }
])
</script>

使用 <template v-for> 时的 key

<template>
  <ul>
    <template v-for="todo in todoList" :key="todo.id">
      <li>{{ todo.content }}</li>
    </template>
  </ul>
</template>

<script setup lang="ts">
import { ref } from 'vue'
interface Todo {
  id: number
  content: string
  isComplete: boolean
}

const todoList = ref<Todo[]>([
  { id: 1, content: '完成 Vue 文档编写', isComplete: false },
  { id: 2, content: '整理列表渲染案例', isComplete: true }
])
</script>

数组变化侦测

Vue 能够侦听响应式数组的变更方法,并在调用时触发视图更新

这些变更方法包括:

  • push:向数组末尾添加元素

    // 向用户列表追加新数据,视图自动更新
    userList.value.push({ id: 3, account: 'admin3' })
  • pop:移除数组最后一个元素

    // 删除最后一位用户
    userList.value.pop()
  • shift:移除数组第一个元素

    // 删除首位用户
    userList.value.shift()
  • unshift:向数组开头添加元素

    // 在列表开头添加用户
    userList.value.unshift({ id: 0, account: 'superAdmin' })
  • splice:删除、插入或替换元素

    // 从索引 1 开始删除 1 个元素
    userList.value.splice(1, 1)
  • sort:数组排序

    // 按用户 id 升序排列
    userList.value.sort((a, b) => a.id - b.id)
  • reverse:颠倒数组顺序

    // 反转用户列表顺序
    userList.value.reverse()

代码案例

<template>
  <div class="user-box">
    <h3>数组方法操作列表</h3>
    <button @click="addUser">push 追加用户</button>
    <ul>
      <li v-for="item in userList" :key="item.id">{{ item.account }}</li>
    </ul>
  </div>
</template>

<script setup lang="ts">
import { ref } from 'vue'
interface User {
  id: number
  account: string
}
// 全程使用同一份用户数据源
const userList = ref<User[]>([
  { id: 1, account: 'admin1' },
  { id: 2, account: 'admin2' }
])
// 定义方法操作数组,案例完整连贯
const addUser = () => {
  userList.value.push({
    id: userList.value.length + 1,
    account: `admin${userList.value.length + 1}`
  })
}
</script>