在 Vue 开发中,条件渲染(v-if
/v-else
)和循环渲染(v-for
)是最常用的模板语法,它们让我们能够根据数据动态控制 DOM 元素的显示与列表渲染。
接下来跟随我的脚步,一起来看看这两种语法的用法、区别。
一、条件渲染:v-if /v-else/v-else-if
条件渲染用于根据表达式的真假来动态显示或隐藏元素,Vue 提供了 v-if
、v-else
、v-else-if
三个指令实现这一功能。
1. 基本用法
<template> <div class="condition-example"> <!-- v-if:表达式为真时渲染 --> <p v-if="isVisible">这是 v-if 渲染的内容</p> <!-- v-else:与 v-if 搭配使用,当 v-if 为假时渲染 --> <p v-else>这是 v-else 渲染的内容</p> <!-- v-else-if:多条件判断 --> <div v-if="score >= 90">优秀</div> <div v-else-if="score >= 60">及格</div> <div v-else>不及格</div> </div> </template> <script setup> import { ref } from 'vue'; // 控制 v-if/v-else 的显示 const isVisible = ref(true); // 控制多条件判断 const score = ref(85); </script>
核心特点:
v-if
是 “真正的条件渲染”—— 当条件为假时,元素会被完全从 DOM 中移除,而非仅隐藏;v-else
和v-else-if
必须紧跟在v-if
或v-else-if
后面,否则无法识别;- 条件表达式可以是任何返回布尔值的 JavaScript 表达式(如
score >= 90
、isLogin && hasPermission
)。
2. v-if 与 v-show 的区别
Vue 还提供 v-show
指令用于条件显示,它与 v-if
的核心区别在于元素是否被移除:
<template> <div> <!-- v-if:条件为假时元素被移除 --> <p v-if="isVisible">v-if 内容</p> <!-- v-show:条件为假时元素被隐藏(添加 display: none 样式) --> <p v-show="isVisible">v-show 内容</p> </div> </template>
特性 | v-if | v-show |
---|---|---|
渲染方式 | 条件为真时才渲染元素 | 始终渲染元素,通过样式控制显示 / 隐藏 |
DOM 操作 | 会触发元素的创建 / 销毁 | 仅修改样式,元素始终存在 |
初始渲染成本 | 条件为假时,初始渲染成本低 | 无论条件真假,初始渲染成本高 |
切换成本 | 条件切换时,成本高(DOM 操作) | 切换成本低(仅修改样式) |
适用场景 | 条件很少切换(如权限判断) | 条件频繁切换(如标签页切换) |
3. 条件渲染的最佳实践
(1)避免在同一元素上使用 v-if 和 v-for
Vue 官方明确建议不要在同一元素上同时使用 v-if
和 v-for
,因为 v-for
的优先级高于 v-if
,会导致性能问题(循环中每次都要判断条件)。
错误示例:
<!-- 不推荐:v-for 和 v-if 同时使用 --> <ul> <li v-for="item in list" v-if="item.active" :key="item.id"> {{ item.name }} </li> </ul>
正确做法:先通过计算属性过滤数据,再循环渲染:
<template> <ul> <!-- 推荐:先过滤再循环 --> <li v-for="item in activeItems" :key="item.id"> {{ item.name }} </li> </ul> </template> <script> import { ref, computed } from 'vue'; const list = ref([ { id: 1, name: '选项1', active: true }, { id: 2, name: '选项2', active: false }, { id: 3, name: '选项3', active: true } ]); // 计算属性过滤数据 const activeItems = computed(() => { return list.value.filter(item => item.active); }); </script>
(2)使用 <template>
包裹多元素条件渲染
当需要条件渲染多个元素时,可使用 <template>
作为包裹容器(不会被渲染到 DOM 中):
<template> <!-- 使用 template 包裹多元素,避免额外 DOM 节点 --> <template v-if="hasPermission"> <h3>权限内容标题</h3> <p>这是需要权限才能查看的内容</p> <button>操作按钮</button> </template> </template>
(3)为条件渲染的元素添加 key 提升性能
当切换 v-if
/v-else
时,Vue 可能会复用已有元素以优化性能。若需避免复用(如表单输入场景),可添加 key
标识:
<template> <div> <template v-if="isEditing"> <input type="text" key="edit-input" placeholder="编辑模式"> </template> <template v-else> <input type="text" key="view-input" placeholder="查看模式"> </template> <button @click="isEditing = !isEditing">切换模式</button> </div> </template> <script> import { ref } from 'vue'; const isEditing = ref(false); </script>
添加不同 key
后,切换模式时输入框会被重新创建,而非复用,避免输入内容意外保留。
二、循环渲染:v-for
v-for
用于基于数组或对象渲染列表,是处理动态数据列表的核心指令。
1. 基本用法
(1)遍历数组
<template> <ul> <!-- 基本用法:item 为数组元素 --> <li v-for="item in items" :key="item.id"> {{ item.name }} </li> <!-- 带索引:(item, index) 接收元素和索引 --> <li v-for="(item, index) in items" :key="item.id"> {{ index + 1 }}. {{ item.name }} </li> </ul> </template> <script> import { ref } from 'vue'; const items = ref([ { id: 1, name: '苹果' }, { id: 2, name: '香蕉' }, { id: 3, name: '橙子' } ]); </script>
(2)遍历对象
<template> <ul> <!-- 遍历对象:(value, key) 接收值和键名 --> <li v-for="(value, key) in user" :key="key"> {{ key }}: {{ value }} </li> <!-- 带索引:(value, key, index) 接收值、键名和索引 --> <li v-for="(value, key, index) in user" :key="key"> {{ index + 1 }}. {{ key }}: {{ value }} </li> </ul> </template> <script> import { reactive } from 'vue'; const user = reactive({ name: '张三', age: 20, gender: '男' }); </script>
(3)遍历整数
v-for
还可以直接遍历整数,用于渲染固定次数的元素:
<template> <div> <!-- 渲染 5 个星号 --> <span v-for="n in 5" :key="n">★</span> </div> </template>
2. key 属性的重要性
v-for
渲染列表时,必须为每个项添加唯一的 key
属性,这是 Vue 虚拟 DOM Diff 算法的关键,用于识别列表项的身份,优化渲染性能。
<!-- 正确:使用唯一 ID 作为 key --> <li v-for="item in items" :key="item.id"> {{ item.name }} </li>
为什么需要 key?
当列表数据变化时(如增删、排序),Vue 会通过 key
判断哪些元素是新增的、删除的或移动的,从而只更新变化的部分,而非重新渲染整个列表。
key 的选择原则:
- 优先使用数据本身的唯一标识(如后端返回的
id
); - 避免使用索引(
index
)作为 key—— 当列表排序或删除中间项时,索引会变化,导致 key 失效,反而影响性能; - 若数据确实没有唯一标识,可使用
Symbol
或其他方式生成临时唯一 key。
3. 数组更新检测
Vue 对数组的一些方法进行了包裹,使得这些方法修改数组后会自动触发视图更新,这些方法被称为 “响应式数组方法”:
-
修改原数组的方法(会触发更新):
push()
:添加元素到末尾pop()
:删除末尾元素shift()
:删除第一个元素unshift()
:添加元素到开头splice()
:添加 / 删除 / 替换元素sort()
:排序reverse()
:反转
-
返回新数组的方法(需替换原数组才会触发更新):
filter()
concat()
slice()
<template> <div> <ul> <li v-for="item in fruits" :key="item.id">{{ item.name }}</li> </ul> <button @click="addFruit">添加水果</button> <button @click="filterFruits">筛选苹果</button> </div> </template> <script> import { ref } from 'vue'; const fruits = ref([ { id: 1, name: '苹果' }, { id: 2, name: '香蕉' }, { id: 3, name: '橙子' } ]); // 使用 push() 修改原数组(自动更新视图) const addFruit = () => { fruits.value.push({ id: 4, name: '草莓' }); }; // 使用 filter() 返回新数组(需替换原数组才更新视图) const filterFruits = () => { fruits.value = fruits.value.filter(item => item.name === '苹果'); }; </script>
注意:直接通过索引修改数组元素(如 fruits.value[0] = { ... }
)不会触发视图更新,需使用 splice
或替换整个数组:
// 错误:直接修改索引,不触发更新 fruits.value[0] = { id: 1, name: '红苹果' }; // 正确:使用 splice 修改 fruits.value.splice(0, 1, { id: 1, name: '红苹果' }); // 正确:替换整个数组 fruits.value = [...fruits.value.slice(0, 0), { id: 1, name: '红苹果' }, ...fruits.value.slice(1)];
4. 循环渲染的最佳实践
(1)始终指定 key 且确保唯一
如前所述,key
是优化列表渲染的关键,必须添加且确保唯一:
<!-- 推荐 --> <li v-for="item in products" :key="item.id"> {{ item.name }} </li> <!-- 不推荐:使用索引作为 key --> <li v-for="(item, index) in products" :key="index"> {{ item.name }} </li>
(2)避免在 v-for 中修改原数据
循环渲染时,应避免直接在模板中修改数组元素(如 v-for
内部使用 v-model
绑定元素属性),推荐通过方法统一处理:
<template> <ul> <li v-for="item in items" :key="item.id"> <input type="text" :value="item.name" @input="(e) => updateItemName(item.id, e.target.value)" > </li> </ul> </template> <script> import { ref } from 'vue'; const items = ref([ { id: 1, name: '商品1' }, { id: 2, name: '商品2' } ]); // 统一方法更新数据,逻辑更清晰 const updateItemName = (id, newName) => { const item = items.value.find(item => item.id === id); if (item) { item.name = newName; } }; </script>
(3)使用计算属性处理列表数据
当需要对列表进行过滤、排序等操作时,优先使用计算属性处理,而非在 v-for
中直接处理,提高代码可读性和性能:
<template> <div> <input v-model="searchText" placeholder="搜索商品"> <ul> <!-- 渲染过滤后的列表 --> <li v-for="item in filteredItems" :key="item.id"> {{ item.name }} </li> </ul> </div> </template> <script> import { ref, computed } from 'vue'; const items = ref([ { id: 1, name: '苹果手机' }, { id: 2, name: '华为平板' }, { id: 3, name: '小米手表' } ]); const searchText = ref(''); // 计算属性过滤数据 const filteredItems = computed(() => { return items.value.filter(item => item.name.toLowerCase().includes(searchText.value.toLowerCase()) ); }); </script>
(4)控制循环渲染的范围
对于大型列表(如 1000+ 项),直接全部渲染会导致性能问题,可采用 “分页” 或 “虚拟滚动” 优化:
<!-- 分页示例 --> <template> <div> <ul> <li v-for="item in currentPageItems" :key="item.id"> {{ item.name }} </li> </ul> <button @click="currentPage--" :disabled="currentPage === 1">上一页</button> <span>第 {{ currentPage }} 页</span> <button @click="currentPage++" :disabled="currentPage >= totalPages">下一页</button> </div> </template> <script> import { ref, computed } from 'vue'; // 模拟大量数据 const items = ref(Array.from({ length: 100 }, (_, i) => ({ id: i + 1, name: `项目 ${i + 1}` }))); const pageSize = 10; // 每页显示 10 条 const currentPage = ref(1); // 当前页码 // 计算当前页数据 const currentPageItems = computed(() => { const start = (currentPage.value - 1) * pageSize; const end = start + pageSize; return items.value.slice(start, end); }); // 计算总页数 const totalPages = computed(() => { return Math.ceil(items.value.length / pageSize); }); </script>
三、总结
-
条件渲染:
v-if
完全移除 / 创建元素,v-show
仅隐藏 / 显示元素,根据场景选择;- 避免
v-if
和v-for
同时使用,优先用计算属性过滤数据; - 多元素条件渲染用
<template>
包裹,减少额外 DOM 节点。
-
循环渲染:
- 始终为
v-for
项添加唯一key
,优先使用数据的唯一标识; - 熟悉响应式数组方法,直接修改索引不会触发更新;
- 大型列表需分页或虚拟滚动优化性能;
- 用计算属性处理过滤、排序等逻辑,提高代码可读性。
- 始终为
好了好了,Vue 条件与循环渲染分享到此结束!