06Vue 3 列表渲染:概念与用法详解

一、核心概念

1.1 什么是列表渲染

列表渲染是将数组或对象的数据映射到 DOM 元素的过程,Vue 3 使用 v-for 指令来实现这一功能。

1.2 主要特性

  • 基于数据源动态生成多个元素

  • 自动响应数据变化

  • 支持多种数据源类型

  • 提供唯一 key 管理

二、基本用法

2.1 数组渲染

<template>
  <!-- 基本用法 -->
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
  
  <!-- 获取索引 -->
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      {{ index + 1 }}. {{ item.name }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue'

const items = ref([
  { id: 1, name: '项目一' },
  { id: 2, name: '项目二' },
  { id: 3, name: '项目三' }
])
</script>

2.2 对象渲染

<template>
  <!-- 对象基本渲染 -->
  <ul>
    <li v-for="(value, key) in userInfo" :key="key">
      {{ key }}: {{ value }}
    </li>
  </ul>
  
  <!-- 带索引的对象渲染 -->
  <ul>
    <li v-for="(value, key, index) in userInfo" :key="key">
      {{ index }}. {{ key }}: {{ value }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue'

const userInfo = ref({
  name: '张三',
  age: 25,
  email: '[email protected]'
})
</script>

2.3 使用数字范围

<template>
  <!-- 渲染 1-10 -->
  <div>
    <span v-for="n in 10" :key="n">
      {{ n }}
    </span>
  </div>
  
  <!-- 渲染指定范围 -->
  <div>
    <span v-for="n in [5, 10, 15, 20]" :key="n">
      {{ n }}
    </span>
  </div>
</template>

三、关键属性 :key 的重要性

3.1 为什么需要 key

<template>
  <!-- 正确使用 key -->
  <div>
    <div 
      v-for="item in items" 
      :key="item.id"  <!-- 推荐使用唯一标识 -->
      class="item"
    >
      {{ item.content }}
    </div>
  </div>
  
  <!-- 不推荐的做法 -->
  <div>
    <div 
      v-for="(item, index) in items" 
      :key="index"    <!-- 仅在列表稳定时使用 -->
      class="item"
    >
      {{ item.content }}
    </div>
  </div>
</template>

<script setup>
const items = ref([
  { id: 'unique-1', content: '内容一' },
  { id: 'unique-2', content: '内容二' }
])
</script>

3.2 key 的最佳实践

  1. 使用唯一标识符(如数据库 ID)

  2. 避免使用数组索引(除非列表从不重新排序)

  3. key 值应该是字符串或数字

四、高级用法

4.1 与 v-if 结合使用

<template>
  <!-- 不推荐:v-for 和 v-if 在同一元素上 -->
  <div>
    <!-- 这种写法性能较差 -->
    <div v-for="item in items" v-if="item.isActive" :key="item.id">
      {{ item.name }}
    </div>
  </div>
  
  <!-- 推荐:使用计算属性或 template 包裹 -->
  <div>
    <template v-for="item in items" :key="item.id">
      <div v-if="item.isActive">
        {{ item.name }}
      </div>
    </template>
  </div>
</template>

<script setup>
import { computed } from 'vue'

// 更好的方式:使用计算属性
const activeItems = computed(() => {
  return items.value.filter(item => item.isActive)
})
</script>

4.2 在组件上使用 v-for

<template>
  <!-- 自定义组件列表 -->
  <ProjectItem
    v-for="project in projects"
    :key="project.id"
    :project="project"
    @select="handleSelect"
  />
</template>

<script setup>
import ProjectItem from './ProjectItem.vue'

const projects = ref([
  { id: 1, title: '项目A' },
  { id: 2, title: '项目B' }
])

const handleSelect = (project) => {
  console.log('选中项目:', project)
}
</script>

4.3 嵌套循环

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

<script setup>
const categories = ref([
  {
    id: 1,
    name: '电子产品',
    products: [
      { id: 101, name: '手机' },
      { id: 102, name: '电脑' }
    ]
  },
  {
    id: 2,
    name: '书籍',
    products: [
      { id: 201, name: 'Vue 3 指南' },
      { id: 202, name: 'JavaScript 高级编程' }
    ]
  }
])
</script>

五、性能优化

5.1 使用计算属性减少渲染

<script setup>
import { computed, ref } from 'vue'

const rawItems = ref([
  { id: 1, name: 'Item 1', active: true },
  { id: 2, name: 'Item 2', active: false },
  { id: 3, name: 'Item 3', active: true }
])

// 使用计算属性过滤和排序
const displayItems = computed(() => {
  return rawItems.value
    .filter(item => item.active)
    .sort((a, b) => a.name.localeCompare(b.name))
})
</script>

5.2 虚拟滚动(大数据量)

<template>
  <!-- 使用第三方库如 vue-virtual-scroller -->
  <RecycleScroller
    class="scroller"
    :items="largeList"
    :item-size="50"
    key-field="id"
  >
    <template #default="{ item }">
      <div class="item">
        {{ item.name }}
      </div>
    </template>
  </RecycleScroller>
</template>

<script setup>
import { ref } from 'vue'

const largeList = ref(
  Array.from({ length: 10000 }, (_, i) => ({
    id: i + 1,
    name: `项目 ${i + 1}`
  }))
)
</script>

<style scoped>
.scroller {
  height: 400px;
}
.item {
  height: 50px;
  line-height: 50px;
  border-bottom: 1px solid #eee;
}
</style>

六、响应式更新

6.1 数组更新检测

<script setup>
import { ref } from 'vue'

const items = ref(['A', 'B', 'C'])

// 这些方法会触发视图更新
const updateArray = () => {
  // 变异方法(自动触发更新)
  items.value.push('D')      // 添加
  items.value.pop()          // 移除最后一个
  items.value.shift()        // 移除第一个
  items.value.unshift('Z')   // 添加到开头
  items.value.splice(1, 1)   // 删除/替换
  items.value.sort()         // 排序
  items.value.reverse()      // 反转
  
  // 非变异方法(需要重新赋值)
  items.value = items.value.filter(item => item !== 'B')
  items.value = items.value.concat(['E', 'F'])
  items.value = [...items.value, 'G']
}
</script>

6.2 对象更新检测

<script setup>
import { reactive } from 'vue'

const user = reactive({
  name: '张三',
  age: 25
})

// 添加新属性(需要特殊处理)
const addProperty = () => {
  // 方法1:使用 Vue.set(Vue 2 风格)
  // import { set } from 'vue'
  // set(user, 'email', '[email protected]')
  
  // 方法2:直接赋值(推荐)
  user.email = '[email protected]'
  
  // 方法3:使用 Object.assign
  Object.assign(user, { address: '北京' })
}
</script>

七、实际应用示例

7.1 待办事项列表

<template>
  <div class="todo-app">
    <input 
      v-model="newTodo"
      @keyup.enter="addTodo"
      placeholder="添加新任务"
    />
    
    <ul>
      <li 
        v-for="(todo, index) in todos" 
        :key="todo.id"
        :class="{ completed: todo.completed }"
      >
        <input 
          type="checkbox" 
          v-model="todo.completed"
        />
        <span>{{ todo.text }}</span>
        <button @click="removeTodo(index)">删除</button>
      </li>
    </ul>
    
    <div>总计: {{ totalTodos }}, 完成: {{ completedTodos }}</div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const newTodo = ref('')
const todos = ref([])
let idCounter = 1

const addTodo = () => {
  if (newTodo.value.trim()) {
    todos.value.push({
      id: idCounter++,
      text: newTodo.value,
      completed: false
    })
    newTodo.value = ''
  }
}

const removeTodo = (index) => {
  todos.value.splice(index, 1)
}

const totalTodos = computed(() => todos.value.length)
const completedTodos = computed(() => 
  todos.value.filter(todo => todo.completed).length
)
</script>

<style scoped>
.todo-app {
  max-width: 400px;
  margin: 0 auto;
}

.completed {
  text-decoration: line-through;
  color: #999;
}

li {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 5px 0;
}
</style>

八、注意事项

  1. 避免同时使用 v-for 和 v-if:优先使用计算属性过滤数据

  2. 始终提供唯一的 key:提高性能,避免渲染问题

  3. 避免直接修改数组长度:使用 splice 代替

  4. 大数据量使用虚拟滚动:提升渲染性能

  5. 复杂数据结构考虑组件化:提高可维护性

总结

Vue 3 的列表渲染功能强大而灵活,通过 v-for 指令可以轻松处理各种数据结构的渲染需求。合理使用 key、结合计算属性优化性能、遵循最佳实践,可以构建出高效且易维护的列表界面。

记住核心原则:让数据驱动视图,Vue 会自动处理数据变化到视图更新的过程。

posted @ 2026-02-10 14:52  麻辣~香锅  阅读(4)  评论(0)    收藏  举报