在 Vue 开发中,条件渲染(v-if
/v-else
)和循环渲染(v-for
)是最常用的模板语法,它们让我们能够根据数据动态控制 DOM 元素的显示与列表渲染。
接下来跟随我的脚步,一起来看看这两种语法的用法、区别。
一、条件渲染:v-if /v-else/v-else-if
条件渲染用于根据表达式的真假来动态显示或隐藏元素,Vue 提供了 v-if
、v-else
、v-else-if
三个指令实现这一功能。
1. 基本用法
vue
<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
的核心区别在于元素是否被移除:
vue
<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
,会导致性能问题(循环中每次都要判断条件)。
错误示例:
vue
<!-- 不推荐:v-for 和 v-if 同时使用 -->
<ul>
<li v-for="item in list" v-if="item.active" :key="item.id">
{{ item.name }}
</li>
</ul>
正确做法:先通过计算属性过滤数据,再循环渲染:
vue
<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 中):
vue
<template>
<!-- 使用 template 包裹多元素,避免额外 DOM 节点 -->
<template v-if="hasPermission">
<h3>权限内容标题</h3>
<p>这是需要权限才能查看的内容</p>
<button>操作按钮</button>
</template>
</template>
(3)为条件渲染的元素添加 key 提升性能
当切换 v-if
/v-else
时,Vue 可能会复用已有元素以优化性能。若需避免复用(如表单输入场景),可添加 key
标识:
vue
<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)遍历数组
vue
<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)遍历对象
vue
<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
还可以直接遍历整数,用于渲染固定次数的元素:
vue
<template>
<div>
<!-- 渲染 5 个星号 -->
<span v-for="n in 5" :key="n">★</span>
</div>
</template>
2. key 属性的重要性
v-for
渲染列表时,必须为每个项添加唯一的 key
属性,这是 Vue 虚拟 DOM Diff 算法的关键,用于识别列表项的身份,优化渲染性能。
vue
<!-- 正确:使用唯一 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()
vue
<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
或替换整个数组:
vue
// 错误:直接修改索引,不触发更新
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
是优化列表渲染的关键,必须添加且确保唯一:
vue
<!-- 推荐 -->
<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
绑定元素属性),推荐通过方法统一处理:
vue
<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
中直接处理,提高代码可读性和性能:
vue
<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+ 项),直接全部渲染会导致性能问题,可采用 "分页" 或 "虚拟滚动" 优化:
vue
<!-- 分页示例 -->
<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 条件与循环渲染分享到此结束!