理解 Vue 的列表渲染:从传统 DOM 到响应式世界的演进

在 Web 开发中,渲染列表数据是前端最基础也最频繁的任务之一。过去我们习惯使用原生 JavaScript 操作 DOM,通过循环动态创建元素节点,但随着前端框架的发展,Vue 等现代框架用更高效、声明式的方式重塑了这一过程。

本文将从传统做法讲起,逐步过渡到 Vue 的 v-for,并探讨它的用法场景,帮助你更系统地掌握 Vue 列表渲染的能力。


一、传统做法:手动操作 DOM

我们先来看看最传统的方式:

js 复制代码
<ul id="todo-list"></ul>

<script>
  const todos = [
    { name: '学习 JavaScript' },
    { name: '了解 DOM 操作' },
    { name: '尝试 Vue 框架' }
  ];

  const ul = document.getElementById('todo-list');

  todos.forEach(todo => {
    const li = document.createElement('li');
    li.textContent = todo.name;
    ul.appendChild(li);
  });
</script>

这段代码做了什么?

  • 手动获取 DOM 元素

  • 遍历数组并为每一项创建 DOM 元素

  • 插入到页面中

虽然清晰,但一旦数据变化(比如添加、删除项),就必须重新操作 DOM,极其繁琐、容易出错,而且性能难以保证。


二、Vue 的声明式语法:v-for 入门

Vue 使用 v-for 指令让列表渲染变得优雅自然:

js 复制代码
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>

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

const items = ref([
  { id: 1, name: '学习 JavaScript' },
  { id: 2, name: '了解 Vue' },
  { id: 3, name: '构建项目' }
])
</script>

🧠 解读:

  • v-for="item in items":遍历响应式数组 items

  • :key="item.id":为每个元素设置唯一 key,便于 Vue 高效地追踪变化

  • 使用 ref 创建响应式数据源

优点:无需关心 DOM 操作,数据变动自动驱动视图更新。


三、变量作用域与索引值

v-for 支持访问索引值,还能使用父级作用域的变量:

js 复制代码
<template>
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      {{ parentLabel }} - {{ index + 1 }}: {{ item.name }}
    </li>
  </ul>
</template>

<script setup>
const parentLabel = '待办事项'
const items = ref([
  { id: 1, name: '写文章' },
  { id: 2, name: '整理笔记' }
])
</script>

四、对象的遍历

不仅数组可以遍历,对象同样适用:

js 复制代码
<template>
  <ul>
    <li v-for="(value, key, index) in userInfo" :key="key">
      {{ index }}. {{ key }}: {{ value }}
    </li>
  </ul>
</template>

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

const userInfo = reactive({
  name: '小明',
  age: 25,
  city: '北京'
})
</script>

五、基于范围的遍历(整数渲染)

js 复制代码
<template>
  <span v-for="n in 5" :key="n">{{ n }} </span>
</template>

输出:1 2 3 4 5

注意:n 从 1 开始,而不是 0


六、嵌套 v-for 与作用域访问

js 复制代码
<template>
  <ul>
    <li v-for="group in groups" :key="group.title">
      <h3>{{ group.title }}</h3>
      <ul>
        <li v-for="user in group.users" :key="user.id">
          {{ user.name }}
        </li>
      </ul>
    </li>
  </ul>
</template>

<script setup>
const groups = ref([
  {
    title: '开发组',
    users: [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
  },
  {
    title: '产品组',
    users: [{ id: 3, name: 'Cathy' }]
  }
])
</script>

子作用域可以访问父级变量,如 group.title。


七、v-if 与 v-for 的冲突与解决

js 复制代码
<!-- 错误示范:todo 未定义 -->
<li v-for="todo in todos" v-if="!todo.done">
  {{ todo.text }}
</li>

修复方式:

js 复制代码
<template v-for="todo in todos" :key="todo.id">
  <li v-if="!todo.done">
    {{ todo.text }}
  </li>
</template>

或者使用计算属性预先过滤:

js 复制代码
const visibleTodos = computed(() => todos.value.filter(todo => !todo.done))

八、组件中使用 v-for 并传参

js 复制代码
<template>
  <TodoItem
    v-for="(todo, index) in todos"
    :key="todo.id"
    :item="todo"
    :index="index"
  />
</template>

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

const todos = ref([
  { id: 1, text: '学 Vue' },
  { id: 2, text: '写博客' }
])
</script>

TodoItem.vue:

js 复制代码
<template>
  <li>{{ index + 1 }}. {{ item.text }}</li>
</template>

<script setup>
defineProps(['item', 'index'])
</script>

九、响应式数组变化与不可变操作

js 复制代码
// ✅ 可侦测的变更方法
items.value.push({ id: 4, name: '复习知识点' })

// ✅ 替换为新数组(推荐)
items.value = items.value.filter(item => item.name.includes('Vue'))

计算属性过滤:

js 复制代码
const filteredItems = computed(() => {
  return items.value.filter(item => item.name.includes('Vue'))
})

避免修改原数组:

js 复制代码
// ❌ 会改变原数组
// return items.value.reverse()

// ✅ 安全做法
return [...items.value].reverse()

🔚 总结:Vue 的 v-for 相比传统方法的优势

维度 传统 JavaScript DOM 操作 Vue 的 v-for
语法简洁 ❌ 需手动创建元素、插入 DOM ✅ 声明式语法
数据响应 ❌ 修改数据不自动更新视图 ✅ 响应式更新
可维护性 ❌ 容易出错、难以维护 ✅ 易读、易维护
组件复用 ❌ 手动处理数据与 DOM 分离 ✅ 配合组件优雅封装
性能优化 ❌ 手动管理元素重排 ✅ Vue 自动 diff 算法优化更新
作用域访问 ❌ 局部作用域难控制 ✅ 嵌套作用域清晰

📌 写在最后

从手动构建列表到使用 Vue 的 v-for,我们经历的是从命令式到声明式、从混乱到有序的技术演进。这不仅提升了开发效率,也让代码更具表达力和可维护性。

相关推荐
踏上青云路10 分钟前
C# 闭包
java·前端·c#
myjs99919 分钟前
数学=符号
java·前端·算法
喝拿铁写前端22 分钟前
Flutter 学习笔记 - 搭建(macOS 版)
前端·flutter
天下权1 小时前
抛弃脚手架!手写极简Vue2实现原理
前端
张元清1 小时前
Neant:0心智负担的React状态管理库
前端·javascript·面试
阳树阳树1 小时前
小程序蓝牙API能力探索 1——蓝牙协议发展历史
前端
yuki_uix1 小时前
部署个人网页?如下几款套餐了解一下呢 :)
前端
阿华的代码王国1 小时前
【Android】PopupWindow实现长按菜单
android·xml·java·前端·后端
亚里士多德芙1 小时前
前端实现视频Banner + 滚屏视频
前端
Lethe1 小时前
类小红书的社交卡片瀑布流
前端·vue.js