基本列表(v-for)
1、v-for指令
(1)用于展示列表数据
(2)语法v-for="(item, index) in xxx" :key="yyy"
(3)可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
2、案例:(展示数组的遍历和对象的遍历)
<body>
<!--准备好一个容器-->
<div id="root">
<!--遍历数组-->
<h2>人员列表</h2>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}}-{{p.age}}
</li>
</ul>
<!--遍历对象-->
<h2>人员列表</h2>
<ul>
<li v-for="(value,k) of cars" :key="k">
{{k}}-{{value}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20},
],
cars:{
name:'奥迪A8',
price:'70万',
color:'黑色'
}
}
})
</script>
key的作用与原理
1、Vue官方解释:
key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。
2、面试题:react、Vue中的key有什么作用?(key的内部原理)
(1)虚拟DOM中key的作用: key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据新数据生成新的虚拟DOM,随后Vue进行新虚拟DOM与旧虚拟DOM的差异比较,比较规则如下:
- 规则一:旧虚拟DOM中找到了与新虚你DOM相同的key:
若虚拟DOM中内容没有改变,直接使用之前的真实DOM;
若虚拟DOM中内容发生了改变,则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
- 规则二:旧虚拟DOM中未找到与新虚拟DOM相同的key:
创建真实DOM,随后渲染到页面。
(2)用index作为key可能会引发的问题:
- 若对数据进行:逆序添加、逆序删除等破坏顺序操作:
会产生没有必要的真实DOM更新 ==> 界面效果没问题,但效率低
- 如果结构中还包含输入类的DOM:
会产生错误DOM更新 ==> 界面有问题
(3)开发中如何选择key?
最好使用每条数据的唯一标识作为key,比如id,手机号、身份证号、学好等唯一值。
如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
3、案例分析:
- 需求:创建一个列表,列表中会显示人员信息和一个文本框,写一个方法,在列表顶部插入老刘这个人。 代码:
<body>
<!--准备好一个容器-->
<div id="root">
<h2>人员列表</h2>
<button @click = "add">添加一个老刘</button>
<ul>
<li v-for="(p,index) of persons" :key="index">
{{p.name}} -- {{p.age}}
<input type="text" />
</li>
</ul>
</div>
</body>
<script type="text/javascript">
const vm = new Vue({
el:'#root',
data:{
persons:[
{id:'001',name:'张三',age:18},
{id:'002',name:'李四',age:19},
{id:'003',name:'王五',age:20},
],
},
methods:{
add(){
const p = {id:'004',name:'老刘',age:90}
this.persons.unshift(p);
}
}
})
</script>
- 操作一:在每个文本框中添加文字信息,如下图所示:
- 操作二:点击按钮,如下图所示:
可以发现文本框内容错乱。
案例问题分析
遍历列表时会产生虚拟DOM,我们用VNode表示。
-
遍历列表一开始,我们传入数据,生成虚拟DOM,然后将虚拟DOM转换为真实DOM,如图:原理一左侧所示;
-
当点击按钮后产生新数据,根据新数据生成新的虚拟DOM,如图:原理一右侧前两个框框所示:
-
当要产生新数据要从新虚拟DOM生成新的真实DOM时,需要进行下列操作:
(1)将新虚拟DOM与初始虚拟DOM中的Vnodes节点进行对比,我们一张三那条数据为例:
对比Vnodes节点是根据key的值来进行的。先找到key=“001”的节点,发现新数据内容是“老刘”,不是“张三”,发生了变化,所以舍弃初始虚拟DOM的Vnode节点中“张三”的数据;
然后再对比<input>标签,发现没有改变 (为什么说<input>中内容没有改变呢?因为我们一开始是从浏览器输入的文本内容,存在本地浏览器的缓存中,并不会作为对比的依据。),最后生成真实的DOM节点,包括:老刘和原来的输入框,即图:原理一中右下角白色框框内的第一条数据。剩余的节点依此类推。
----------------------------------- 图:原理一 -----------------------------------
-
由上述分析可知:key是虚拟DOM进行更新的重要内容,如果使用默认的key在遇到文本框中有残留文本时更改数据顺序会造成数据错位的问题,所以要合理设置key值,并且保证key值唯一、不重复,能够唯一识别出某条数据。
-
接下来我们分析一下:将id作为key值后,将“老刘”插入头部时发生的变化。 更改内容
<li v-for="(p,index) of persons" :key="p.id">
分析虚拟DOM对比时的过程:
首先,根据key找到新DOM中和初始DOM中key=“004”的两条数据,没有在初始DOM中找到,于是将新虚拟DOM中的文本内容加载为真实DOM;
然后,根据key找到新DOM中和初始DOM中key=“001”的两条数据,对比发现数据没有变化,于是将初始DOM中的内容加载为真实DOM;
具体过程如图:原理二,展示结果如图:结果,发现问题得到解决。
----------------------------------- 图:原理二 -----------------------------------
----------------------------------- 图:结果 -----------------------------------
虚拟DOM的对比算法(diff算法)
1、设计diff算法目的:复用DOM节点
2、diff算法核心:
找到满足 key = "xxx" 的新DOM中的Vnode2 和 初始DOM的Vnode1;
逐一对比Vnode中的标签内容,如果:
(1)在初始DOM中找不到满足条件的Vnode1,将Vnode2加载为真实DOM;
(2)在初始DOM中找到了满足条件的Vnode1:
- 如果发现了Vnode1与Vnode2内容相同,将Vnode1加载为真实DOM;
- 如果发现了Vnode1与Vnode2内容不同,用Vnode2中的数据作为结果,并加载到真实DOM中。
然后重复上述操作,直到新DOM中的虚拟节点遍历完毕。
列表过滤
功能:在文本框中输入关键词,查找并显示列表中包含关键词的人
效果:如下图
实现:
<body>
<!--准备好一个容器-->
<div id="root">
<!--遍历数组-->
<h2>人员列表</h2>
<input type="text" placeholder="请输入名字:" v-model="keyWord" />
<ul>
<li v-for="(p,index) of filPersons" :key="index">
{{p.name}}-{{p.age}}-{{p.sex}}
</li>
</ul>
</div>
</body>
<script type="text/javascript">
//用watch实现
// const vm = new Vue({
// el:'#root',
// data:{
// keyWord:"",
// persons:[
// {id:'001',name:'马冬梅',age:19,sex:'女'},
// {id:'002',name:'周冬雨',age:20,sex:'女'},
// {id:'002',name:'周杰伦',age:21,sex:'男'},
// {id:'003',name:'温兆伦',age:22,sex:'男'},
// ],
// newPersons:[],
// },
// watch:{
// keyWord:{
// immediate:true,
// handler(val){
// this.newPersons = this.persons.filter((p) => {
// return p.name.indexOf(val) !== -1
// })
// }
// }
// }
// })
//用计算属性实现
const vm = new Vue({
el:'#root',
data:{
keyWord:"",
persons:[
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'002',name:'周杰伦',age:21,sex:'男'},
{id:'003',name:'温兆伦',age:22,sex:'男'},
],
},
computed:{
filPersons(){
return this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1
})
}
}
})
</script>
列表排序
功能:通过按不同的按钮实现对数据的排序
效果:
--- 原顺序:
--- 年龄降序:
--- 年龄升序:
实现:
<body>
<!--准备好一个容器-->
<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) of filPersons" :key="p.id">
{{p.name}}-{{p.age}}-{{p.sex}}
<input type="text" />
</li>
</ul>
</div>
</body>
<script type="text/javascript">
//用计算属性实现
const vm = new Vue({
el:'#root',
data:{
keyWord:"",
sortType:0,//0代表原顺序,1代表降序,2代表升序,
persons:[
{id:'001',name:'马冬梅',age:19,sex:'女'},
{id:'002',name:'周冬雨',age:20,sex:'女'},
{id:'002',name:'周杰伦',age:21,sex:'男'},
{id:'003',name:'温兆伦',age:22,sex:'男'},
],
},
computed:{
filPersons(){
const arr = this.persons.filter((p) => {
return p.name.indexOf(this.keyWord) !== -1
})
if(this.sortType){
arr.sort((a,b) => {
return this.sortType === 1 ? (b.age - a.age) : (a.age < b.age)
})
}
return arr
}
}
})
</script>