第一部分:Vue 3 基础入门
第10集:条件渲染与列表渲染入门
在Vue应用中,条件渲染和列表渲染是非常常见的操作。Vue提供了简洁、高效的指令来处理这些场景。在本集中,我们将学习Vue 3的条件渲染和列表渲染基础,包括v-if、v-show、v-for等指令的使用方法和最佳实践。
10.1 条件渲染
条件渲染是指根据条件来决定是否渲染某个元素或组件。Vue提供了v-if和v-show两个指令来实现条件渲染。
10.1.1 v-if 指令
v-if指令用于条件性地渲染元素或组件。当条件为真时,元素或组件会被渲染;当条件为假时,元素或组件会被销毁。
基本使用:
<template>
<div>
<h1>条件渲染</h1>
<button @click="toggleShow">切换显示</button>
<div v-if="isShow">
<h2>这是v-if渲染的内容</h2>
<p>条件为真时显示,为假时销毁</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isShow = ref(true)
function toggleShow() {
isShow.value = !isShow.value
}
</script>10.1.2 v-else-if 和 v-else
v-if可以与v-else-if和v-else配合使用,实现多条件分支渲染。
示例:
<template>
<div>
<h1>多条件渲染</h1>
<input type="number" v-model.number="score" placeholder="输入分数">
<div v-if="score >= 90">
<h2>优秀</h2>
<p>你的分数是 {{ score }},非常棒!</p>
</div>
<div v-else-if="score >= 80">
<h2>良好</h2>
<p>你的分数是 {{ score }},继续努力!</p>
</div>
<div v-else-if="score >= 60">
<h2>及格</h2>
<p>你的分数是 {{ score }},刚刚及格,需要加油!</p>
</div>
<div v-else-if="score >= 0">
<h2>不及格</h2>
<p>你的分数是 {{ score }},需要更加努力!</p>
</div>
<div v-else>
<h2>请输入有效分数</h2>
<p>分数必须是大于等于0的数字</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const score = ref(0)
</script>10.1.3 v-show 指令
v-show指令用于条件性地显示元素或组件。无论条件是否为真,元素或组件都会被渲染,只是通过CSS的display属性来控制显示或隐藏。
示例:
<template>
<div>
<h1>v-show 示例</h1>
<button @click="toggleShow">切换显示</button>
<div v-show="isShow">
<h2>这是v-show渲染的内容</h2>
<p>无论条件真假,元素都会被渲染,只是通过CSS控制显示/隐藏</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isShow = ref(true)
function toggleShow() {
isShow.value = !isShow.value
}
</script>10.1.4 v-if vs v-show
| 特性 | v-if | v-show |
|---|---|---|
| 渲染方式 | 条件为真时渲染,为假时销毁 | 始终渲染,通过CSS display控制 |
| 性能开销 | 切换时开销大 | 初始渲染开销大 |
| 适用场景 | 条件不频繁切换 | 条件频繁切换 |
| 支持template | 是 | 否 |
| 支持v-else | 是 | 否 |
| 元素生命周期 | 触发完整的生命周期 | 不触发生命周期 |
使用建议:
- 如果条件不频繁切换,使用
v-if - 如果条件频繁切换,使用
v-show - 如果需要使用
v-else或template,使用v-if
10.1.5 在 template 元素上使用 v-if
我们可以在template元素上使用v-if,这样可以条件渲染一组元素,而不需要额外的DOM元素来包裹它们。
示例:
<template>
<div>
<h1>在template上使用v-if</h1>
<button @click="toggleShow">切换显示</button>
<template v-if="isShow">
<h2>标题</h2>
<p>第一段内容</p>
<p>第二段内容</p>
<ul>
<li>列表项1</li>
<li>列表项2</li>
<li>列表项3</li>
</ul>
</template>
</div>
</template>
<script setup>
import { ref } from 'vue'
const isShow = ref(true)
function toggleShow() {
isShow.value = !isShow.value
}
</script>10.2 列表渲染
列表渲染是指根据数据列表来渲染一组元素或组件。Vue提供了v-for指令来实现列表渲染。
10.2.1 v-for 基本使用
v-for指令用于遍历数组或对象,并为每个元素或属性渲染一个元素或组件。
遍历数组:
<template>
<div>
<h1>列表渲染</h1>
<h2>遍历数组</h2>
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index + 1 }}. {{ item.name }} - {{ item.price }}元
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: '苹果', price: 5.5 },
{ id: 2, name: '香蕉', price: 3.0 },
{ id: 3, name: '橙子', price: 4.0 },
{ id: 4, name: '草莓', price: 10.0 },
{ id: 5, name: '葡萄', price: 8.0 }
])
</script>遍历对象:
<template>
<div>
<h2>遍历对象</h2>
<ul>
<li v-for="(value, key, index) in user" :key="key">
{{ index + 1 }}. {{ key }}: {{ value }}
</li>
</ul>
</div>
</template>
<script setup>
import { reactive } from 'vue'
const user = reactive({
name: '张三',
age: 25,
email: 'zhangsan@example.com',
address: '北京市朝阳区'
})
</script>遍历数字:
<template>
<div>
<h2>遍历数字</h2>
<ul>
<li v-for="n in 5" :key="n">
第 {{ n }} 项
</li>
</ul>
</div>
</template>10.2.2 key 属性
在使用v-for时,我们需要为每个渲染的元素提供一个唯一的key属性。key属性的作用是帮助Vue识别每个元素的身份,从而高效地更新DOM。
为什么需要key?
- Vue使用key来跟踪每个节点的身份
- 当列表数据变化时,Vue会根据key来决定如何更新DOM
- 正确使用key可以提高渲染性能,避免不必要的DOM操作
key的选择:
- 优先使用唯一的ID作为key,如数据库中的主键
- 避免使用索引作为key,尤其是当列表涉及排序、过滤、删除等操作时
- 对于简单的静态列表,可以使用索引作为key
示例:
<!-- 推荐:使用唯一ID作为key -->
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
<!-- 不推荐:使用索引作为key -->
<li v-for="(item, index) in items" :key="index">
{{ item.name }}
</li>10.2.3 数组更新检测
Vue能够检测到数组的以下变更方法,并自动触发视图更新:
push():向数组末尾添加元素pop():删除数组末尾的元素shift():删除数组第一个元素unshift():向数组开头添加元素splice():插入、删除或替换数组元素sort():对数组进行排序reverse():反转数组
示例:
<template>
<div>
<h1>数组更新检测</h1>
<ul>
<li v-for="item in items" :key="item.id">
{{ item.name }}
</li>
</ul>
<div class="buttons">
<button @click="addItem">添加元素</button>
<button @click="removeItem">删除元素</button>
<button @click="sortItems">排序</button>
<button @click="reverseItems">反转</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, name: '苹果' },
{ id: 2, name: '香蕉' },
{ id: 3, name: '橙子' }
])
function addItem() {
items.value.push({ id: Date.now(), name: '新水果' })
}
function removeItem() {
items.value.pop()
}
function sortItems() {
items.value.sort((a, b) => a.name.localeCompare(b.name))
}
function reverseItems() {
items.value.reverse()
}
</script>
<style scoped>
.buttons {
margin-top: 20px;
}
button {
margin-right: 10px;
padding: 8px 16px;
background-color: #42b883;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>注意事项:
- Vue不能检测到通过索引直接修改数组元素,如
items[0] = newItem - Vue不能检测到修改数组长度,如
items.length = 0
解决方法:
- 使用
Vue.set(items, index, newValue)或this.$set(items, index, newValue)(Vue 2) - 使用
items.splice(index, 1, newValue) - 使用扩展运算符创建新数组:
items.value = [...items.value.slice(0, index), newValue, ...items.value.slice(index + 1)]
10.2.4 在 template 上使用 v-for
我们可以在template元素上使用v-for,这样可以渲染一组元素,而不需要额外的DOM元素来包裹它们。
示例:
<template>
<div>
<h1>在template上使用v-for</h1>
<template v-for="item in items" :key="item.id">
<h2>{{ item.title }}</h2>
<p>{{ item.content }}</p>
<hr>
</template>
</div>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, title: '标题1', content: '内容1' },
{ id: 2, title: '标题2', content: '内容2' },
{ id: 3, title: '标题3', content: '内容3' }
])
</script>10.2.5 v-for 与 v-if 一起使用
在Vue 3中,不建议在同一个元素上同时使用v-for和v-if,因为v-if的优先级高于v-for,这会导致v-if无法访问到v-for的变量。
不推荐的用法:
<!-- 不推荐:v-if无法访问到v-for的变量 -->
<li v-for="item in items" :key="item.id" v-if="item.isActive">
{{ item.name }}
</li>推荐的用法:
- 使用计算属性过滤数据
- 在
template上使用v-for,然后在内部元素上使用v-if
示例:
<template>
<div>
<h1>v-for与v-if一起使用</h1>
<!-- 方法1:使用计算属性 -->
<h2>方法1:使用计算属性</h2>
<ul>
<li v-for="item in activeItems" :key="item.id">
{{ item.name }}
</li>
</ul>
<!-- 方法2:在template上使用v-for -->
<h2>方法2:在template上使用v-for</h2>
<ul>
<template v-for="item in items" :key="item.id">
<li v-if="item.isActive">
{{ item.name }}
</li>
</template>
</ul>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const items = ref([
{ id: 1, name: '苹果', isActive: true },
{ id: 2, name: '香蕉', isActive: false },
{ id: 3, name: '橙子', isActive: true },
{ id: 4, name: '草莓', isActive: false },
{ id: 5, name: '葡萄', isActive: true }
])
// 方法1:使用计算属性过滤数据
const activeItems = computed(() => {
return items.value.filter(item => item.isActive)
})
</script>10.3 条件渲染与列表渲染的综合示例
让我们创建一个综合示例,结合条件渲染和列表渲染来实现一个简单的商品列表,包括筛选、排序、添加、删除等功能。
示例:
<template>
<div class="product-app">
<h1>商品列表</h1>
<!-- 添加商品 -->
<div class="add-product">
<h2>添加商品</h2>
<input
type="text"
v-model="newProduct.name"
placeholder="商品名称"
>
<input
type="number"
v-model.number="newProduct.price"
placeholder="商品价格"
>
<button @click="addProduct">添加</button>
</div>
<!-- 筛选和排序 -->
<div class="filter-sort">
<h2>筛选和排序</h2>
<div>
<label for="filter">筛选:</label>
<select id="filter" v-model="filter">
<option value="all">全部</option>
<option value="expensive">价格 > 5元</option>
<option value="cheap">价格 <= 5元</option>
</select>
</div>
<div>
<label for="sort">排序:</label>
<select id="sort" v-model="sortBy">
<option value="name">按名称</option>
<option value="price">按价格</option>
</select>
<button @click="toggleSortOrder">
{{ sortOrder === 'asc' ? '升序' : '降序' }}
</button>
</div>
</div>
<!-- 商品列表 -->
<div class="product-list">
<h2>商品列表</h2>
<!-- 条件渲染:当没有商品时显示提示 -->
<div v-if="filteredProducts.length === 0" class="no-products">
<p>没有找到商品</p>
</div>
<!-- 列表渲染:显示商品列表 -->
<table v-else>
<thead>
<tr>
<th>序号</th>
<th>名称</th>
<th>价格</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(product, index) in filteredProducts" :key="product.id">
<td>{{ index + 1 }}</td>
<td>{{ product.name }}</td>
<td>{{ product.price }}元</td>
<td>
<button @click="deleteProduct(product.id)">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 统计信息 -->
<div class="stats">
<h2>统计信息</h2>
<p>商品总数:{{ products.length }}</p>
<p>显示数量:{{ filteredProducts.length }}</p>
<p>平均价格:{{ averagePrice.toFixed(2) }}元</p>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// 商品列表
const products = ref([
{ id: 1, name: '苹果', price: 5.5 },
{ id: 2, name: '香蕉', price: 3.0 },
{ id: 3, name: '橙子', price: 4.0 },
{ id: 4, name: '草莓', price: 10.0 },
{ id: 5, name: '葡萄', price: 8.0 }
])
// 新商品
const newProduct = ref({
name: '',
price: 0
})
// 筛选条件
const filter = ref('all')
// 排序条件
const sortBy = ref('name')
const sortOrder = ref('asc')
// 计算属性:筛选和排序后的商品列表
const filteredProducts = computed(() => {
let result = [...products.value]
// 筛选
if (filter.value === 'expensive') {
result = result.filter(product => product.price > 5)
} else if (filter.value === 'cheap') {
result = result.filter(product => product.price <= 5)
}
// 排序
result.sort((a, b) => {
if (sortBy.value === 'name') {
return sortOrder.value === 'asc' ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name)
} else {
return sortOrder.value === 'asc' ? a.price - b.price : b.price - a.price
}
})
return result
})
// 计算属性:平均价格
const averagePrice = computed(() => {
if (products.value.length === 0) return 0
const total = products.value.reduce((sum, product) => sum + product.price, 0)
return total / products.value.length
})
// 方法:添加商品
function addProduct() {
if (newProduct.value.name && newProduct.value.price > 0) {
products.value.push({
id: Date.now(),
name: newProduct.value.name,
price: newProduct.value.price
})
// 重置表单
newProduct.value.name = ''
newProduct.value.price = 0
}
}
// 方法:删除商品
function deleteProduct(id) {
products.value = products.value.filter(product => product.id !== id)
}
// 方法:切换排序顺序
function toggleSortOrder() {
sortOrder.value = sortOrder.value === 'asc' ? 'desc' : 'asc'
}
</script>
<style scoped>
.product-app {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.add-product, .filter-sort, .product-list, .stats {
margin-bottom: 30px;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
.add-product input {
margin-right: 10px;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 8px 16px;
background-color: #42b883;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
margin-right: 10px;
}
.filter-sort div {
margin-bottom: 10px;
}
select {
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
margin-right: 10px;
}
.product-list table {
width: 100%;
border-collapse: collapse;
}
.product-list th, .product-list td {
padding: 10px;
border: 1px solid #ddd;
text-align: left;
}
.product-list th {
background-color: #f0f0f0;
}
.no-products {
text-align: center;
padding: 20px;
background-color: #f9f9f9;
border: 1px solid #eee;
border-radius: 4px;
}
.stats p {
margin: 5px 0;
}
</style>10.4 条件渲染与列表渲染的最佳实践
合理选择条件渲染指令:
- 条件不频繁切换时使用
v-if - 条件频繁切换时使用
v-show - 需要使用
v-else或template时使用v-if
- 条件不频繁切换时使用
正确使用key属性:
- 优先使用唯一ID作为key
- 避免使用索引作为key,尤其是当列表涉及排序、过滤、删除等操作时
- 确保key的唯一性
避免在同一元素上同时使用v-for和v-if:
- 使用计算属性过滤数据
- 在
template上使用v-for,然后在内部元素上使用v-if
优化长列表性能:
- 考虑使用虚拟滚动技术(如vue-virtual-scroller)
- 合理使用
v-memo指令缓存组件 - 避免在列表中进行复杂的计算
保持模板简洁:
- 将复杂的条件判断逻辑提取到计算属性或方法中
- 将复杂的列表项拆分为单独的组件
- 避免在模板中写过多的JavaScript代码
处理空状态:
- 为条件渲染和列表渲染添加适当的空状态提示
- 提供友好的用户反馈
本集小结
在本集中,我们学习了Vue 3的条件渲染和列表渲染基础:
条件渲染:
- 使用
v-if、v-else-if、v-else实现条件渲染 - 使用
v-show实现条件显示 - 了解了
v-if和v-show的区别和适用场景 - 学习了在
template上使用v-if
- 使用
列表渲染:
- 使用
v-for遍历数组、对象和数字 - 了解了key属性的重要性和正确用法
- 学习了数组更新检测和注意事项
- 学习了在
template上使用v-for - 了解了如何正确地将
v-for和v-if一起使用
- 使用
综合示例:
- 实现了一个完整的商品列表应用
- 结合了条件渲染和列表渲染
- 实现了添加、删除、筛选、排序等功能
- 使用了计算属性来优化代码
最佳实践:
- 合理选择条件渲染指令
- 正确使用key属性
- 避免在同一元素上同时使用
v-for和v-if - 优化长列表性能
- 保持模板简洁
- 处理空状态
条件渲染和列表渲染是Vue应用开发中的核心功能,掌握好这些知识可以帮助我们创建复杂、交互丰富的应用。到目前为止,我们已经完成了Vue 3基础入门部分的学习,包括环境搭建、项目结构、单文件组件、模板语法、响应式数据、事件处理、表单绑定、条件渲染和列表渲染等内容。
在下一部分中,我们将学习Vue 3的组件化开发基础,包括组件定义、组件通信、插槽等内容。让我们继续深入学习Vue 3!