Skip to content

VueJS - 渲染

本章介绍了如何在 Vue 中根据 JavaScript 表达式有条件地渲染元素,以及如何动态地渲染列表项。

Vue 提供了像 v-if、v-else、v-else-if 和 v-show 这样的指令,用于根据 JavaScript 表达式的真假值来控制元素的渲染或显示。

v-if 有条件地渲染一个元素或模板片段。只有当指令的表达式评估为真值时,该元素及其内容才会被渲染。v-else 为 v-if 提供一个“else”块,而 v-else-if 提供一个“else if”块。

关键在于,v-else 和 v-else-if 元素必须紧接在一个 v-if 或 v-else-if 元素之后。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 Conditional Rendering</title>
<script src="https://unpkg.com/vue@3"></script>
<style>
button {
margin: 5px;
padding: 8px 12px;
}
p { margin-top: 10px; font-weight: bold; }
</style>
</head>
<body>
<div id="app">
<button @click="score = 95">Set Score High</button>
<button @click="score = 70">Set Score Medium</button>
<button @click="score = 40">Set Score Low</button>
<button @click="score = null">Clear Score</button>
<p v-if="score === null">
Score not set.
</p>
<p v-else-if="score >= 90">
Grade: A (Score: {{ score }})
</p>
<p v-else-if="score >= 60">
Grade: C (Score: {{ score }})
</p>
<p v-else>
Grade: F (Score: {{ score }})
</p>
<!-- 在 <template> 上使用 v-if 进行分组 -->
<template v-if="score !== null && score < 60">
<p style="color: red;">This requires attention!</p>
</template>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const score = ref(null);
return { score };
}
}).mount('#app');
</script>
</body>
</html>

在此示例中:

  • 我们使用 ref 来保存 score。
  • 按钮修改 score 的值。
  • v-if/v-else-if/v-else 链根据分数显示相应的等级。
  • 在任何时候,只有链中的一个块会被渲染。
  • <template> 标签与 v-if 一起使用,用于有条件地渲染一组元素(在此例中只有一个段落),而不会在 DOM 中额外渲染一个包裹元素。

尝试点击按钮。你会看到输出文本根据 score 的变化而改变。使用浏览器开发者工具检查 DOM:注意当条件改变时,<p> 标签是如何从 DOM 结构中实际添加或移除的。

v-show 也根据真/假表达式有条件地显示元素。与 v-if 的主要区别在于,带有 v-show 的元素总是会被渲染到 DOM 中。它只是简单地通过 CSS 的 display 属性进行切换(当条件为 false 时,添加 display: none;)。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 v-show Example</title>
<script src="https://unpkg.com/vue@3"></script>
</head>
<body>
<div id="app">
<button @click="isVisible = !isVisible">
Toggle Visibility (v-show)
</button>
<h2 v-show="isVisible" style="color: blue;">
This element uses v-show. It's always in the DOM.
</h2>
<button @click="isRendered = !isRendered">
Toggle Rendering (v-if)
</button>
<h2 v-if="isRendered" style="color: green;">
This element uses v-if. It's added/removed from the DOM.
</h2>
</div>
<script>
const { createApp, ref } = Vue;
createApp({
setup() {
const isVisible = ref(true);
const isRendered = ref(true);
return { isVisible, isRendered };
}
}).mount('#app');
</script>
</body>
</html>

切换这两个按钮并检查浏览器开发者工具中的元素:

  • v-show 元素(蓝色文本)将始终存在于 DOM 结构中,但当 isVisible 为 false 时,它会添加内联样式 style="display: none;"。
  • v-if 元素(绿色文本)当 isRendered 为 false 时,将完全从 DOM 结构中消失。
  • v-if 的切换开销更高,因为它涉及从 DOM 添加/移除元素。它的初始渲染开销较低,因为如果条件最初为 false,则不会渲染该元素。当条件不常改变时,或当你想确保内部的组件/元素在切换时真正被销毁和重新创建时,请使用 v-if。
  • v-show 的切换开销较低,因为它只更改 CSS display 属性。它的初始渲染开销较高,因为元素总是会被渲染。当你需要频繁切换某个元素时,请使用 v-show。

常见陷阱:v-show 不能用于 <template> 元素,也不能与 v-else 或 v-else-if 一起使用。

v-for 指令用于根据数组、对象、范围或其它可迭代源来渲染列表项。

  • 遍历数组:v-for="item in items" 或 v-for="(item, index) in items"
  • 遍历对象:v-for="value in myObject" 或 v-for="(value, key) in myObject" 或 v-for="(value, key, index) in myObject"
  • 遍历范围:v-for="n in 10"(渲染 1, 2, …, 10)

在 v-for 的作用域内,你可以完全访问父级作用域的属性以及当前项(item、value、n)以及可选的索引或键。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Vue 3 List Rendering</title>
<script src="https://unpkg.com/vue@3"></script>
<style>
input { margin-bottom: 10px; padding: 5px; }
ul { list-style: none; padding: 0; }
li { background-color: #eee; margin: 5px; padding: 10px; border-radius: 4px; }
li span { font-weight: bold; margin-right: 5px; }
</style>
</head>
<body>
<div id="app">
<input type="text" v-model="newFruit" @keyup.enter="addFruit" placeholder="Enter a fruit and press Enter">
<h2>My Fruits:</h2>
<p v-if="fruits.length === 0">No fruits yet!</p>
<ul v-else>
<!-- Using item and index -->
<li v-for="(fruit, index) in fruits" :key="fruit.id">
<span>{{ index + 1 }}.</span> {{ fruit.name }}
<button @click="removeFruit(fruit.id)" style="float: right;">X</button>
</li>
</ul>
<h2>Object Iteration:</h2>
<ul>
<li v-for="(value, key) in fruitInfo" :key="key">
<span>{{ key }}:</span> {{ value }}
</li>
</ul>
</div>
<script>
const { createApp, ref, reactive } = Vue;
createApp({
setup() {
const fruits = ref([
{ id: 1, name: 'Apple' },
{ id: 2, name: 'Banana' },
{ id: 3, name: 'Orange' }
]);
const newFruit = ref('');
let nextId = 4;
const fruitInfo = reactive({
color: 'Red',
taste: 'Sweet',
season: 'Autumn'
});
const addFruit = () => {
if (newFruit.value.trim()) {
fruits.value.push({ id: nextId++, name: newFruit.value.trim() });
newFruit.value = ''; // Clear input
}
};
const removeFruit = (idToRemove) => {
fruits.value = fruits.value.filter(fruit => fruit.id !== idToRemove);
};
return { fruits, newFruit, addFruit, removeFruit, fruitInfo };
}
}).mount('#app');
</script>
</body>
</html>

在此示例中:

  • 我们使用 v-for="(fruit, index) in fruits" 来遍历 fruits 数组。
  • 我们显示 index(加 1 以便人类阅读)和 fruit.name。
  • 我们使用 v-for="(value, key) in fruitInfo" 来遍历 fruitInfo 对象的属性。
  • 输入框允许向响应式的 fruits 数组添加新的水果,列表会自动更新。

在使用 v-for 时,强烈建议为每个项提供一个唯一的 key 属性::key="uniqueValue"。这有助于 Vue 高效地识别每个节点,使其能够重用和重新排序现有元素,而不是从头创建它们。

key 为什么重要?

  • 性能 (Performance): 允许 Vue 的 diff 算法在列表改变(添加、移除或重新排序项)时更加高效。
  • 状态保留 (State Preservation): 有助于保留列表中组件或元素的状态。没有 key,Vue 可能会为不同的项重用一个元素,导致意外的状态。
  • 过渡 (Transitions): 对于使用 <TransitionGroup> 正确应用过渡至关重要(将在“过渡与动画”章节中介绍)。

请使用来自数据中的唯一标识符(例如数据库中的 ID)作为 key。避免使用循环索引作为 key,除非列表项的顺序不会改变,或者项不会从中间添加/移除,否则这会失去高效跟踪的目的。

常见陷阱:忘记 :key 属性或使用非唯一值(例如当顺序改变时使用索引)可能导致渲染错误和性能问题。

通常不建议在同一个元素上同时使用 v-if 和 v-for,因为隐式的优先级可能会令人困惑(在 Vue 3 中,v-if 具有更高的优先级)。相反,你可以通过两种主要方式实现条件列表渲染:

  • 过滤源数组 (Filter the source array): 使用计算属性返回过滤后的列表,然后对计算属性使用 v-for。
  • 在循环内部进行条件渲染 (Conditional rendering inside the loop): 将 v-if 放在 v-for 循环内部的一个内部元素(或 <template>)上。
// 选项 1:计算属性(推荐)
const visibleFruits = computed(() => fruits.value.filter(f => f.isVisible));
// 模板:
// <li v-for="fruit in visibleFruits" :key="fruit.id">{{ fruit.name }}</li>
// 选项 2:在 v-for 内部使用 v-if
// <template v-for="fruit in fruits" :key="fruit.id">
// <li v-if="fruit.isVisible">{{ fruit.name }}</li>
// </template>

使用计算属性过滤源数据通常更清晰且性能更好。

进一步阅读:Vue 官方文档涵盖了条件渲染:https://vuejs.org/guide/essentials/conditional.html 和列表渲染:https://vuejs.org/guide/essentials/list.html