Vue2--列表渲染

784 阅读5分钟

基本列表(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>
  • 操作一:在每个文本框中添加文字信息,如下图所示:

image.png

  • 操作二:点击按钮,如下图所示:

image.png

可以发现文本框内容错乱。

案例问题分析

遍历列表时会产生虚拟DOM,我们用VNode表示。

  1. 遍历列表一开始,我们传入数据,生成虚拟DOM,然后将虚拟DOM转换为真实DOM,如图:原理一左侧所示;

  2. 当点击按钮后产生新数据,根据新数据生成新的虚拟DOM,如图:原理一右侧前两个框框所示:

  3. 当要产生新数据要从新虚拟DOM生成新的真实DOM时,需要进行下列操作:

(1)将新虚拟DOM与初始虚拟DOM中的Vnodes节点进行对比,我们一张三那条数据为例:

对比Vnodes节点是根据key的值来进行的。先找到key=“001”的节点,发现新数据内容是“老刘”,不是“张三”,发生了变化,所以舍弃初始虚拟DOM的Vnode节点中“张三”的数据;

然后再对比<input>标签,发现没有改变 (为什么说<input>中内容没有改变呢?因为我们一开始是从浏览器输入的文本内容,存在本地浏览器的缓存中,并不会作为对比的依据。),最后生成真实的DOM节点,包括:老刘和原来的输入框,即图:原理一中右下角白色框框内的第一条数据。剩余的节点依此类推。

image.png

----------------------------------- 图:原理一 -----------------------------------

  1. 由上述分析可知:key是虚拟DOM进行更新的重要内容,如果使用默认的key在遇到文本框中有残留文本时更改数据顺序会造成数据错位的问题,所以要合理设置key值,并且保证key值唯一、不重复,能够唯一识别出某条数据。

  2. 接下来我们分析一下:将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;

具体过程如图:原理二,展示结果如图:结果,发现问题得到解决。

image.png ----------------------------------- 图:原理二 -----------------------------------

image.png

----------------------------------- 图:结果 -----------------------------------

虚拟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中的虚拟节点遍历完毕。

列表过滤

功能:在文本框中输入关键词,查找并显示列表中包含关键词的人

效果:如下图

image.png

image.png

image.png

实现:

	<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>

列表排序

功能:通过按不同的按钮实现对数据的排序

效果:

--- 原顺序:

image.png

--- 年龄降序:

image.png

--- 年龄升序:

image.png

实现:

	<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>