Vue 列表渲染完全指南:v-for 核心用法、key 原理及数据监测实战(附代码案例)
在 Vue 开发中,列表渲染是高频需求,v-for指令作为核心工具,能高效处理数组、对象等数据的展示。但很多开发者在使用时容易忽略key的正确用法、数据响应式更新等细节,导致页面性能问题或渲染异常。本文将从v-for基础用法入手,深入解析key的底层原理,结合数组方法、列表过滤排序案例,以及 Vue 数据监测机制,帮你彻底掌握 Vue 列表渲染的核心技巧。
一、v-for 指令:列表渲染的基础
v-for是 Vue 专门用于列表渲染的指令,支持遍历数组、对象、字符串及指定次数,核心是 “数据驱动视图”,即数据变化时自动更新页面。
1. 核心语法
v-for="(item, index) in 数据源" :key="唯一标识"
item:数据源中的每一项(数组元素、对象属性值等)。index:可选,当前项的索引值。:key:必填(高效渲染关键),用于标识虚拟 DOM 节点的唯一性。
2. 常见遍历场景
(1)遍历数组(最常用)
适用于展示列表数据,如人员列表、商品列表等。
<div id="root"><h2>人员列表</h2><ul><!-- p为数组中的每个对象,index为索引 --><li v-for="(p, index) in persons" :key="p.id">{{p.name}}--{{p.age}}--{{p.sex}}</li></ul>
</div><script>
new Vue({el: '#root',data: {persons: [{ id: '001', name: '张三', age: 18, sex: '男' },{ id: '002', name: '李四', age: 19, sex: '女' },{ id: '003', name: '王五', age: 20, sex: '男' }]}
})
</script>
(2)遍历对象
适用于展示详情类数据,如商品信息、用户资料等。
<h2>汽车信息</h2>
<ul><!-- value为属性值,k为属性名 --><li v-for="(value, k) of car" :key="k">{{k}}--{{value}}</li>
</ul><script>
data: {car: {name: '奥迪A7',price: '70万',color: '至尊灰'}
}
</script>
(3)遍历字符串(极少用)
遍历字符串的每个字符,实际开发中几乎不用。
<h2>遍历字符串</h2>
<ul><li v-for="(val, index) in str" :key="index">{{index}}--{{val}}</li>
</ul><script>
data: {str: 'hello' // 输出:0-h,1-e,2-l,3-l,4-o
}
</script>
(4)遍历指定次数(极少用)
按次数渲染元素,适用于固定数量的占位布局。
二、key 的底层原理:面试核心考点
key是虚拟 DOM 的唯一标识,直接影响 Vue 的渲染性能和正确性,是面试高频问题,需深入理解其作用机制。
1. 前置知识:真实 DOM vs 虚拟 DOM
(1)真实 DOM 的问题
浏览器解析 HTML 会生成真实 DOM 树,每次操作真实 DOM(如更新内容),都会触发 “DOM 树重构→样式计算→布局→绘制” 的完整流程,频繁操作会导致页面卡顿。
(2)虚拟 DOM 的优势
虚拟 DOM 是 Vue 创建的 JS 对象,用于模拟真实 DOM 结构。数据变化时,Vue 先更新虚拟 DOM,再通过 “diff 算法” 对比新旧虚拟 DOM 的差异,仅将变化部分同步到真实 DOM,减少无效操作,提升性能。
(3)代码对比:真实 DOM vs 虚拟 DOM 性能
<div id="app"></div>
<script>
// 真实DOM操作:10000次更新,每次都操作真实DOM
let box = document.querySelector('#app');
console.time('真实DOM');
for (let i = 0; i <= 10000; i++) {box.innerHTML = i;
}
console.timeEnd('真实DOM'); // 耗时较长// 虚拟DOM思路:先操作JS变量,最后一次更新真实DOM
let num = 0;
console.time('虚拟DOM思路');
for (let i = 0; i <= 10000; i++) {num = i;
}
box.innerHTML = num;
console.timeEnd('虚拟DOM思路'); // 耗时极短
</script>
2. key 的核心作用
key 作为虚拟 DOM 节点的唯一标识,是 diff 算法对比新旧虚拟 DOM 的 “依据”,对比规则如下:
- 旧虚拟 DOM 中找到与新虚拟 DOM 相同的 key:
- 若虚拟 DOM 内容未变,直接复用之前的真实 DOM(性能优化)。
- 若内容改变,生成新的真实 DOM 替换旧节点。
- 旧虚拟 DOM 中未找到相同的 key:创建新的真实 DOM 并渲染。
3. 用 index 作为 key 的隐患
很多开发者习惯用index作为 key,但在数据发生 “逆序添加、逆序删除” 等破坏顺序的操作时,会出现问题:
(1)问题 1:无效 DOM 更新,性能浪费
假设数组逆序添加元素,index会重新分配,导致 Vue 误判 “节点已变更”,触发不必要的真实 DOM 更新。
(2)问题 2:输入类 DOM 渲染异常
若列表包含输入框,index变化会导致输入框的值与数据不匹配。
<div id="root"><button @click="add">添加老刘</button><ul><li v-for="(p, index) in persons" :key="index">{{p.name}}--{{p.age}}<input type="text"> <!-- 输入内容会错乱 --></li></ul>
</div>
<script>
new Vue({el: '#root',data: {persons: [{ id: '001', name: '张三', age: 18 },{ id: '002', name: '李四', age: 19 }]},methods: {add() {this.persons.unshift({ id: '003', name: '老刘', age: 40 }); // 逆序添加}}
})
</script>
4. 正确用法:用唯一标识作为 key
应使用数据的唯一标识(如id、手机号、身份证号)作为 key,确保节点标识的稳定性,避免上述问题。
<li v-for="(p, index) in persons" :key="p.id"> <!-- 用id作为key -->{{p.name}}--{{p.age}}<input type="text">
</li>
5. 结论
- 优先使用数据的唯一标识作为 key(如
id)。 - 若仅用于静态展示,无数据顺序变更,用
index作为 key 也可。
三、数组常用方法:列表数据的增删改查
Vue 对数组的 7 个方法进行了 “包装”,调用这些方法时会自动触发视图更新(响应式),其他方法(如indexOf、filter)需配合响应式 API 使用。
1. 响应式数组方法(直接触发视图更新)
- 增:
push()(尾部添加)、unshift()(头部添加)。 - 删:
pop()(尾部删除)、shift()(头部删除)、splice(index, count)(指定位置删除 / 替换)。 - 改:
sort()(排序)、reverse()(反转)。
2. 非响应式数组方法(需配合其他操作)
(1)filter ():数组过滤
返回满足条件的新数组,不改变原数组,常用于列表筛选。
const arr = [1, 6, 9, 3, 7, 5];
const newArr = arr.filter(item => item >= 5);
console.log(newArr); // [6, 9, 7, 5]
(2)indexOf ():查找元素索引
返回元素在数组 / 字符串中第一次出现的索引,不存在返回-1,常用于判断元素是否存在。
javascript
运行
const arr = ['马冬梅', '周杰伦', '张三'];
console.log(arr.indexOf('马冬梅')); // 0(存在)
console.log(arr.indexOf('李四')); // -1(不存在)
(3)sort ():数组排序
默认按字符串排序,需自定义 comparator 实现数字排序。
// 数字数组排序
const arr = [3, 123, 35, 4];
// 升序:a - b
arr.sort((a, b) => a - b); // [3, 4, 35, 123]
// 降序:b - a
arr.sort((a, b) => b - a); // [123, 35, 4, 3]// 对象数组排序(按id升序)
const arr1 = [{ id: 2 }, { id: 13 }, { id: 6 }];
arr1.sort((a, b) => a.id - b.id);
四、实战案例:列表过滤与排序
结合v-for、计算属性、数组方法,实现带筛选和排序功能的列表,体现 “数据驱动视图” 的核心思想。
1. 列表过滤(按关键词搜索)
通过v-model绑定搜索关键词,用filter()过滤数据,支持实时筛选。
<div id="root"><h2>人员列表</h2><input type="text" placeholder="输入名字搜索" v-model="keyWord"><ul><li v-for="(p, index) in filPersons" :key="p.id">{{p.name}}--{{p.age}}--{{p.sex}}</li></ul>
</div><script>
new Vue({el: '#root',data: {keyWord: '',persons: [{ id: '001', name: '马冬梅', age: 18, sex: '女' },{ id: '002', name: '周杰伦', age: 20, sex: '男' },{ id: '003', name: '马东雨', age: 19, sex: '女' }]},computed: {// 计算属性:实时返回过滤后的数组filPersons() {return this.persons.filter(p => {// 包含关键词则保留(不区分大小写可加toLowerCase())return p.name.indexOf(this.keyWord) !== -1;});}}
})
</script>
2. 列表排序(升序 / 降序 / 原序)
在过滤基础上添加排序功能,通过按钮切换排序类型。
<div id="root"><h2>人员列表</h2><input type="text" placeholder="输入名字搜索" v-model="keyWord"><button @click="sortType=2">年龄升序</button><button @click="sortType=1">年龄降序</button><button @click="sortType=0">恢复原序</button><ul><li v-for="(p, index) in filPersons" :key="p.id">{{p.name}}--{{p.age}}--{{p.sex}}</li></ul>
</div><script>
new Vue({el: '#root',data: {keyWord: '',sortType: 0, // 0-原序,1-降序,2-升序persons: [{ id: '001', name: '马冬梅', age: 30, sex: '女' },{ id: '002', name: '周杰伦', age: 18, sex: '男' },{ id: '003', name: '马东雨', age: 33, sex: '女' }]},computed: {filPersons() {// 先过滤const arr = this.persons.filter(p => {return p.name.indexOf(this.keyWord) !== -1;});// 再排序if (this.sortType !== 0) {arr.sort((a, b) => {return this.sortType === 2 ? a.age - b.age : b.age - a.age;});}return arr;}}
})
</script>
五、Vue 数据监测:确保列表响应式更新
Vue 能监测data中数据的变化并触发视图更新,但对对象、数组的监测有特殊规则,若操作不当会导致 “数据变了,视图不变”。
1. 数据监测原理
(1)监测对象数据
Vue 通过 “数据劫持”(Object.defineProperty)给对象的属性添加setter和getter,属性变化时触发setter,进而更新视图。注意:新添加的对象属性默认不具备响应式,需用Vue.set()或vm.$set()手动处理。
(2)监测数组数据
Vue 没有劫持数组的索引,而是通过包装数组方法(如push、splice),调用方法时触发视图更新。注意:直接修改数组索引(如this.arr[0] = 10)不会触发视图更新。
2. 响应式更新的正确操作
(1)对象数据:添加响应式属性
// 给student对象添加sex属性(响应式)
Vue.set(this.student, 'sex', '男');
// 或
this.$set(this.student, 'sex', '男');
(2)数组数据:修改元素的正确方式
// 方式1:使用包装方法
this.student.hobby.push('看电影'); // 新增
this.student.hobby.splice(0, 1, '打篮球'); // 替换
this.student.hobby.sort(); // 排序// 方式2:使用Vue.set()
Vue.set(this.student.hobby, 0, '打篮球');
3. 常见问题:数据变了,视图不变
// 错误:直接修改数组索引,非响应式
this.persons[0] = { id: '001', name: '马老师', age: 50 };// 正确:用splice替换
this.persons.splice(0, 1, { id: '001', name: '马老师', age: 50 });// 错误:给对象新增属性,非响应式
this.student.sex = '男';// 正确:用Vue.set()
Vue.set(this.student, 'sex', '男');
4. 数据监测总结
- Vue 监测
data中所有层次的数据。 - 对象:新增属性需用
Vue.set(),否则非响应式。 - 数组:修改元素需用包装方法或
Vue.set(),直接修改索引无效。 Vue.set()不能给vm或vm的根数据对象添加属性。
六、总结
Vue 列表渲染的核心是v-for指令,配合key优化渲染性能,结合数组方法和计算属性实现复杂功能,同时需遵循 Vue 数据监测规则确保响应式更新。关键要点:
v-for支持数组、对象等数据源,key需用唯一标识。- 虚拟 DOM 通过
key对比差异,提升渲染效率。 - 数组响应式修改依赖包装方法,对象新增属性需用
Vue.set()。 - 列表过滤排序优先用计算属性,实现数据与视图的联动。
掌握这些知识点,能轻松应对 Vue 列表渲染的各类场景,避免常见坑点,提升开发效率和页面性能。
