Vue 列表渲染详解_vue 渲染table
v-for 基本用法
v-for 是 Vue 中用于基于数组渲染列表的核心指令,它的基本语法是 item in items,其中:
- items 是源数据数组(响应式数据)
- item 是当前迭代项的别名,用于在模板中访问当前项的数据
示例代码:
// 定义响应式数组
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<!-- 模板中使用 v-for 渲染列表 -->
<li v-for="item in items">
{{ item.message }}
</li>
索引与父作用域访问
v-for 支持第二个可选参数,表示当前项的索引位置:
// 父作用域数据
const parentMessage = ref('Parent')
// 列表数据
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<!-- 使用索引和父作用域数据 -->
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
输出结果:
Parent - 0 - Foo
Parent - 1 - Bar
作用域说明:
- v-for 块内可以访问父作用域的属性(如示例中的 parentMessage)
- 迭代变量(item、index)仅在当前 v-for 作用域内有效
- 这与 JavaScript 中的 forEach 回调函数作用域类似
解构赋值
在 v-for 中可以使用对象解构,简化对嵌套数据的访问:
<!-- 解构对象属性 -->
<li v-for="{ message } in items">
{{ message }}
</li>
<!-- 带索引的解构 -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>
多层嵌套 v-for
对于多层嵌套的列表,内层 v-for 可以访问外层作用域的数据:
<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>
迭代语法变体
除了 in 之外,还可以使用 of 作为分隔符,更接近 JavaScript 的迭代器语法:
<div v-for="item of items"></div>
遍历对象
v-for 不仅可以遍历数组,还可以遍历对象的属性:
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
<!-- 遍历对象值 -->
<ul>
<li v-for="value in myObject">
{{ value }}
</li>
</ul>
<!-- 包含属性名 -->
<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>
<!-- 包含索引 -->
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
注意:对象遍历顺序基于 Object.values() 的返回顺序
范围值迭代
v-for 可以直接接受一个整数值,渲染从 1 到该数值的范围:
<!-- 渲染 1 到 10 的数字 -->
<span v-for="n in 10">{{ n }}</span>
注意:范围迭代从 1 开始,而非 0
template 上的 v-for
与 v-if 类似,可以在 <template> 标签上使用 v-for 渲染多个元素的块:
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
这种方式不会额外创建包裹元素,适合需要渲染多个同级元素的场景。
v-for 与 v-if 共存问题
当 v-for 和 v-if 同时存在于同一节点时,v-if 具有更高的优先级,这意味着 v-if 无法访问 v-for 的迭代变量。
不推荐的用法:
<!-- 错误示例:v-if 无法访问 todo -->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
正确的用法:
<!-- 正确:将 v-for 放在 template 上 -->
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
最佳实践:
- 避免同时使用 v-if 和 v-for
- 过滤列表:使用计算属性返回过滤后的数组
- 条件渲染整个列表:将 v-if 移至列表容器元素
通过 key 管理状态
Vue 默认采用"就地更新"策略更新 v-for 渲染的列表,这意味着当数据顺序改变时,Vue 不会移动 DOM 元素,而是更新元素内容。
为了让 Vue 能够跟踪每个节点的标识,从而实现高效的重用和重排序,需要提供唯一的 key 属性:
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
使用 <template v-for> 时,key 应放在 <template> 上:
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
注意事项:
- key 必须绑定唯一的值(字符串或数字)
- 不要使用对象作为 key
- 推荐在大多数情况下使用 key,除非列表非常简单
组件上使用 v-for
可以在组件上直接使用 v-for,但需要显式传递数据作为 props:
<MyComponent
v-for="(item, index) in items"
:item="item" <!-- 传递当前项数据 -->
:index="index" <!-- 传递索引 -->
:key="item.id" <!-- 提供唯一 key -->
/>
注意:Vue 不会自动将迭代数据注入组件,必须显式通过 props 传递,这使组件更加灵活和可重用。
数组变化侦测
Vue 能够侦听响应式数组的变更,并触发视图更新,主要通过两种方式:
1. 变更方法
Vue 对数组的以下变更方法进行了包装,调用这些方法会自动触发更新:
- push():向数组末尾添加元素
- pop():删除并返回数组最后一个元素
- shift():删除并返回数组第一个元素
- unshift():向数组开头添加元素
- splice():添加/删除数组元素
- sort():排序数组
- reverse():反转数组
示例:
const items = ref([1, 2, 3])
items.value.push(4) // 会触发视图更新
2. 替换数组
对于不会修改原数组的方法(如 filter()、concat()、slice()),需要将返回的新数组赋值给原数组:
// 过滤数组并替换原数组
items.value = items.value.filter(item => item > 2)
优化说明:Vue 会智能重用已有的 DOM 元素,因此替换数组是高效的操作,不会完全重新渲染列表。
展示过滤或排序后的结果
当需要展示数组的过滤或排序版本而不修改原数组时,可以使用计算属性:
const numbers = ref([1, 2, 3, 4, 5])
// 计算属性返回偶数
const evenNumbers = computed(() => {
return numbers.value.filter(n => n % 2 === 0)
})
<!-- 使用计算属性 -->
<li v-for="n in evenNumbers">{{ n }}</li>
复杂场景:在多层嵌套中,可以使用方法返回过滤后的结果:
const sets = ref([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
])
// 过滤偶数的方法
function even(numbers) {
return numbers.filter(number => number % 2 === 0)
}
<ul v-for="numbers in sets">
<li v-for="n in even(numbers)">{{ n }}</li>
</ul>
注意:在计算属性中使用 reverse() 和 sort() 时,应先创建数组副本,避免修改原数组:
// 错误:会修改原数组
return numbers.reverse()
// 正确:创建副本后操作
return [...numbers].reverse()