快速入门Vue3:条件渲染与列表渲染的小秘密,你居然还不知道?

98 阅读14分钟

一、条件渲染:控制DOM的显隐逻辑

条件渲染是Vue中根据状态动态添加/移除DOM元素的核心能力,主要通过v-if系列指令和v-show实现。

1.1 v-if:真正的“条件渲染”

v-if是Vue中最基础的条件指令,它会根据表达式的真假,完全创建或销毁对应的DOM元素(包括其内部的组件和事件监听器)。

基础用法
<template>
  <div class="user-profile">
    <!-- 未登录时显示登录按钮 -->
    <button v-if="!isLoggedIn" @click="login">立即登录</button>
    <!-- 登录后显示用户信息 -->
    <div v-else>
      <img src="avatar.png" alt="头像" />
      <span>欢迎,{{ username }}!</span>
      <button @click="logout">退出登录</button>
    </div>
  </div>
</template>

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

// 登录状态(初始未登录)
const isLoggedIn = ref(false)
// 用户名
const username = ref('')

// 登录逻辑
const login = () => {
  isLoggedIn.value = true
  username.value = 'Vue新手'
}

// 退出逻辑
const logout = () => {
  isLoggedIn.value = false
  username.value = ''
}
</script>
多分支条件:v-else-if

当需要多个条件分支时,用v-else-if连接:

<template>
  <div class="score-display">
    <p v-if="score >= 90">优秀</p>
    <p v-else-if="score >= 70">良好</p>
    <p v-else-if="score >= 60">及格</p>
    <p v-else>不及格</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const score = ref(85) // 显示“良好”
</script>
批量条件渲染:template

若需要多个元素同时满足条件,可使用template包裹(template不会渲染为真实DOM节点):

<template>
  <template v-if="isAdmin">
    <button>管理用户</button>
    <button>查看日志</button>
    <button>设置权限</button>
  </template>
</template>

1.2 v-show:基于CSS的“显示切换”

v-show的作用与v-if类似,但它不会销毁DOM元素,而是通过CSS的display: none属性控制显隐。元素始终存在于DOM中,只是“看不见”。

基础用法
<template>
  <div class="tab-container">
    <button @click="activeTab = 'home'">首页</button>
    <button @click="activeTab = 'about'">关于我们</button>
    
    <!-- 用v-show切换tab内容 -->
    <div v-show="activeTab === 'home'" class="tab-content">首页内容</div>
    <div v-show="activeTab === 'about'" class="tab-content">关于我们</div>
  </div>
</template>

<script setup>
import { ref } from 'vue'
const activeTab = ref('home') // 默认显示首页
</script>

1.3 v-if vs v-show:核心差异与场景选择

特性v-ifv-show
DOM操作销毁/重建元素修改CSS样式(display)
初始渲染成本条件为假时,无成本无论条件真假,都要渲染元素
切换成本条件切换时,成本高(DOM操作)切换成本低(仅修改样式)
应用场景不频繁切换的场景(如登录状态)频繁切换的场景(如tab、折叠面板)

二、列表渲染:遍历数据生成DOM

列表渲染是Vue中将数组/对象数据转换为DOM列表的核心能力,通过v-for指令实现。

2.1 v-for的基本用法

v-for可以遍历数组、对象、字符串、数字,语法为:v-for="(item, index) in data"(数组)或v-for="(value, key, index) in object"(对象)。

1. 遍历数组(最常见)
<template>
  <ul class="todo-list">
    <!-- 遍历todos数组,item是当前项,index是索引 -->
    <li v-for="(todo, index) in todos" :key="todo.id">
      {{ index + 1 }}. {{ todo.text }}
      <button @click="deleteTodo(index)">删除</button>
    </li>
  </ul>
</template>

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

// 待办事项列表
const todos = ref([
  { id: 1, text: '学习Vue条件渲染' },
  { id: 2, text: '掌握v-for的key用法' },
  { id: 3, text: '完成一篇博客' }
])

// 删除待办项
const deleteTodo = (index) => {
  todos.value.splice(index, 1)
}
</script>
2. 遍历对象
<template>
  <ul class="user-info">
    <!-- 遍历user对象,value是属性值,key是属性名 -->
    <li v-for="(value, key) in user" :key="key">
      {{ key }}: {{ value }}
    </li>
  </ul>
</template>

<script setup>
import { ref } from 'vue'
const user = ref({ name: '张三', age: 25, job: '前端开发' })
</script>
3. 遍历字符串/数字
<template>
  <!-- 遍历字符串(每个字符) -->
  <p v-for="char in 'Vue3'" :key="char">{{ char }}</p>
  <!-- 遍历数字(从1到5) -->
  <p v-for="num in 5" :key="num">{{ num }}</p>
</template>

2.2 key:列表渲染的“身份证”

keyv-for必填属性(Vue3会强制报警告),它的作用是给每个列表项一个唯一标识,帮助Vue的虚拟DOM diff算法识别节点,避免错误的复用。

为什么需要key

Vue的diff算法会复用相同key的节点。若没有key或key重复,Vue会错误地复用节点,导致:

  • 输入框内容不更新(如todo项的输入框,删除前面的项后,后面的输入框内容会“串位”);
  • 组件状态丢失(如计数器组件,重新排序后计数不会重置)。
正确的key选择
  • 优先用数据的唯一ID(如后端返回的id);
  • 避免用**索引(index)**作为key(索引会随数组变化而变化,导致key失效)。

错误示例(用index作为key):

<!-- 错误:当数组排序/删除时,index会变化,导致节点复用错误 -->
<li v-for="(todo, index) in todos" :key="index">
  {{ todo.text }}
  <input type="text" placeholder="备注" />
</li>

正确示例(用唯一ID作为key):

<!-- 正确:用todo.id作为唯一标识,无论数组如何变化,节点都能正确复用 -->
<li v-for="(todo, index) in todos" :key="todo.id">
  {{ todo.text }}
  <input type="text" placeholder="备注" />
</li>

2.3 v-forv-if的“正确结合”

Vue3不建议在同一元素上同时使用v-forv-ifv-for优先级更高,会先遍历所有项再判断条件,导致性能浪费)。

正确的做法是:computed属性过滤数据,再用v-for遍历过滤后的数组。

示例:显示未完成的待办项

<template>
  <ul class="uncompleted-todos">
    <!-- 遍历过滤后的未完成列表 -->
    <li v-for="todo in uncompletedTodos" :key="todo.id">
      {{ todo.text }}
    </li>
  </ul>
</template>

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

const todos = ref([
  { id: 1, text: '学习条件渲染', completed: false },
  { id: 2, text: '掌握列表渲染', completed: true },
  { id: 3, text: '写博客', completed: false }
])

// 计算属性:过滤未完成的待办项
const uncompletedTodos = computed(() => {
  return todos.value.filter(todo => !todo.completed)
})
</script>

三、实战:结合条件与列表渲染的Todo应用

我们用条件渲染(显示登录状态)+列表渲染(显示待办项)+计算属性(过滤未完成项),实现一个完整的Todo应用:

<template>
  <div class="todo-app">
    <!-- 登录状态判断 -->
    <div v-if="!isLoggedIn" class="login-section">
      <input v-model="username" placeholder="用户名" />
      <button @click="login">登录</button>
    </div>

    <!-- 登录后显示Todo列表 -->
    <div v-else class="todo-container">
      <h2>未完成的任务</h2>
      <ul>
        <li v-for="todo in uncompletedTodos" :key="todo.id">
          {{ todo.text }}
          <button @click="markAsCompleted(todo)">完成</button>
        </li>
      </ul>

      <h2>已完成的任务</h2>
      <ul class="completed-list">
        <li v-for="todo in completedTodos" :key="todo.id">
          {{ todo.text }}
          <button @click="markAsUncompleted(todo)">撤销</button>
        </li>
      </ul>

      <div class="add-todo">
        <input v-model="newTodoText" placeholder="输入新任务" />
        <button @click="addTodo">添加</button>
      </div>
    </div>
  </div>
</template>

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

// 登录状态
const isLoggedIn = ref(false)
const username = ref('')

// 待办项列表
const todos = ref([
  { id: 1, text: '学习条件渲染', completed: false },
  { id: 2, text: '掌握列表渲染', completed: false },
  { id: 3, text: '写博客', completed: true }
])
const newTodoText = ref('')

// 计算属性:未完成的待办项
const uncompletedTodos = computed(() => {
  return todos.value.filter(t => !t.completed)
})

// 计算属性:已完成的待办项
const completedTodos = computed(() => {
  return todos.value.filter(t => t.completed)
})

// 登录逻辑
const login = () => {
  if (username.value.trim()) {
    isLoggedIn.value = true
  }
}

// 添加待办项
const addTodo = () => {
  if (newTodoText.value.trim()) {
    todos.value.push({
      id: Date.now(), // 用时间戳作为唯一ID
      text: newTodoText.value.trim(),
      completed: false
    })
    newTodoText.value = ''
  }
}

// 标记为完成
const markAsCompleted = (todo) => {
  todo.completed = true
}

// 撤销完成
const markAsUncompleted = (todo) => {
  todo.completed = false
}
</script>

<style scoped>
.todo-app { max-width: 600px; margin: 20px auto; }
.login-section { margin-bottom: 20px; }
.completed-list { color: #888; text-decoration: line-through; }
.add-todo { margin-top: 20px; }
</style>

四、课后Quiz:巩固核心知识点

问题1:v-ifv-show的核心区别是什么?分别适合什么场景?

答案

  • v-if销毁/重建DOM(真正的条件渲染),适合不频繁切换的场景(如登录状态);
  • v-show修改CSS样式(display: none),适合频繁切换的场景(如tab、折叠面板)。

问题2:为什么v-for必须指定key?用index作为key会有什么问题?

答案

  • key是Vue识别节点的唯一标识,用于diff算法,确保节点正确复用;
  • index作为key时,若数组发生排序/新增/删除,index会变化,导致Vue错误地复用节点(如输入框内容“串位”)。

问题3:如何正确结合v-forv-if

答案

  • 不建议在同一元素上同时使用(v-for优先级更高,会先遍历再判断,性能差);
  • 正确做法:用computed属性过滤数组(如过滤未完成的todo),再用v-for遍历过滤后的数组。

五、常见报错与解决方案

1. 报错:“v-for requires a key attribute”

  • 原因v-for渲染的元素未指定唯一key
  • 解决:给v-for的元素添加:key="唯一标识"(如item.id);
  • 预防:养成写v-for必加key的习惯。

2. 报错:“Duplicate keys detected: x. This may cause an update error.”

  • 原因key重复(如多个元素用了相同的id);
  • 解决:确保key的唯一性(如用后端返回的id、时间戳);
  • 预防:避免用index或重复值作为key

3. 报错:“v-if and v-for used on the same element”

  • 原因:同一元素同时使用v-forv-if(Vue3会报警告);
  • 解决:用computed属性过滤数组,再用v-for遍历;
  • 预防:优先用computed处理条件,再渲染列表。

参考链接:
条件渲染:vuejs.org/guide/essen…
列表渲染:vuejs.org/guide/essen…
key的作用:vuejs.org/guide/essen… 阅读完整的文章:快速入门Vue3:条件渲染与列表渲染的小秘密,你居然还不知道?

往期文章归档
免费好用的热门在线工具