vue组件学习

  • A+
所属分类:Web前端
摘要

​ 前面学习了vue的基础指令,了解了相关的钩子函数,这一章学习vue的组件,组件 (Component) 是 Vue.js 最强大的功能之一,组件可以扩展 HTML 元素,封装可重用的代码,组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:


一.文章导读

​ 前面学习了vue的基础指令,了解了相关的钩子函数,这一章学习vue的组件,组件 (Component) 是 Vue.js 最强大的功能之一,组件可以扩展 HTML 元素,封装可重用的代码,组件系统让我们可以用独立可复用的小组件来构建大型应用,几乎任意类型的应用的界面都可以抽象为一个组件树:

菜鸟教程地址:https://www.runoob.com/vue2/vue-tutorial.html

官网学习地址:https://cn.vuejs.org/

入门学习更加推荐菜鸟

二.组件定义

1.全局组件

<body> 		<!-- 创建两个承载容器 --> 		<div id="app"> 			<my-fristcomponent></my-fristcomponent> 		</div>  		<div id="app1"> 			<!-- 在视图层 直接写组件的标签名就可以调用全局组件 --> 			<my-fristcomponent></my-fristcomponent> 		</div> 		<!--  			值得注意的点是 在js中定义了Vue全局组件在视图层中我们的全局组件必须在创建实例的块中使用 		 --> 		<!-- 如下,app2我们没有绑定vue实例所以组件不会生效--> 		<div id="app2"> 			<my-fristcomponent></my-fristcomponent> 		</div> 		<my-fristcomponent></my-fristcomponent>  		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script> 		<script type="text/javascript"> 			/*  				自定义全局组件  my-fristcomponent是自定义组件的标签名 				template是模板的意思,后面写组件的内容 			 */ 			Vue.component('my-fristcomponent', { 				template: "<p>我的第一个组件</p>" 			}); 			/*  				创建 两个vue实例 			 */ 			new Vue({ 				el: "#app" 			}); 			new Vue({ 				el: "#app1" 			}) 		</script> 	</body> 

2.组件驼峰命名

<body> 		<div id="app"> 			<!--  				在使用组件,如果使用驼峰命名,vue会自动解析,我们 				调用时需要用-隔开并使用小写,如下两种方式是可以 			 --> 			<Hello-world></Hello-world> 			<hello-world></hello-world> 			<!-- 错误 --> 			<HelloWorld></HelloWorld> 		</div> 		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script> 		<script type="text/javascript"> 			/* 在创建实例时,我们遵循驼峰命名法 */ 			Vue.component('HelloWorld', { 				template: "<p>HelloWorld</p>" 			})  			new Vue({ 				el: "#app", 			}) 		</script> 	</body> 

3.组件data数据

<body> 		<div id="app"> 			<data-component></data-component> 			<button @click="change">改变</button> 			{{msg}} 		</div> 		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script> 		<script type="text/javascript"> 			Vue.component("dataComponent", { 				/*  					在定义组件时也可以写data,但是data里面必须是一个函数 					且这个函数必须有返回值 				*/ 				data: function() { 					return { 						msg: "data返回值" 					} 				}, 				// 组件中的msg 只可以在该模板中使用 				template: '<p >{{msg}}</p>' 			})  			new Vue({ 				el: "#app", 				data: { 					msg: "121" 				},  				methods: { 					// 定义change返回无法改变组件中的 					change: function() { 						// alert(11); 						this.msg = "1212"; 					} 				} 			}) 		</script> 	</body> 

4.局部组件

前面已经写个全局指令,全局过滤器,局部过滤器之类的,所以局部组件与全局组件的区别是一样的,局部组件只能在当前实例才能使用

<body> 		<div id="app"> 			<my-component></my-component> 		</div> 		<div id="app1"> 			// my-component组件注册不正确 局部组件不能在其他实例中使用 			<my-component></my-component>  		</div> 		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script> 		<script type="text/javascript"> 			var vm = new Vue({ 				el: "#app", 			/* 	data: { 					msg: "局部指令", 				}, */ 				components:{ 					'my-component':{ 						data:function(){ 							return{ 								msg:"局部指令" 							} 						}, 						template:"<p>{{msg}}</p>" 					} 				} 			}); 				new Vue({ 					el:"#app1" 				}) 		</script> 	</body> 

注意:组件模板中的内容只允许存在一个根标签,多了的话只会解析第一个

	// 下面模板定义了两个p标签,相当于两个根标签,那么第二个就不会生效 	template: '<p >{{msg}}</p> <p>{{msg}}</p> 

三.组件之间的传值

1.父组件向子组件传值

  • 父组件发送的形式是以属性的形式绑定值到子组件身上。
  • 然后子组件用属性props接收
  • 在props中使用驼峰形式,模板中需要使用短横线的形式字符串形式的模板中没有这个限制
<body> 		<div id="app"> 			<p>实例中定义的属性内容是: {{pmsg}}</p> 			#### 下面是组件 			<!--  				:content 等同于v-bind:content 				浏览器打印结果: 				我是定义的组件内容 				我是标题 				父组件内容 			 --> 			<!--  				组件定义的模板中使用的值带有props的属性,在视图层使用v-bind绑定 				父组件的数据从而形成赋值,也可以不绑定直接赋值 				eg:<menu-item title="我自动赋值" content="我自动赋值内容"></menu-item> 			 --> 			<menu-item v-bind:title="ptitle" :content="pmsg"></menu-item> 		</div> 		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script> 		<script type="text/javascript"> 			// menu-item 定义全局组件 			Vue.component('menu-item', { 				// 组件 用props 属性来接受父组件传递的参数 				props: ['title', 'content'], 				//组件中 的data必须是函数,且有返回值 				data: function() { 					return { 						msg: "我是定义的组件内容", 						title: "我是定义的组件的标题" 					} 				}, 				// 定义组件模版 				template: '<p><span>{{msg}}</span><br><span>{{title}}</span><br><span>{{content}}</span></p>' 			}); 			new Vue({ 				el: "#app", 				data: { 					pmsg: "父组件内容", 					ptitle: "我是标题" 				} 			}); 		</script> 	</body> 

2.子组件向父组件传值

  • 子组件用$emit()触发事件
  • $emit() 第一个参数为 自定义的事件名称 第二个参数为需要传递的数据
  • 父组件用v-on 监听子组件的事件
<body> 		<div id="app"> 			<div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div> 			<!-- 2 父组件用v-on 监听子组件的事件 				这里 enlarge-text  是从 $emit 中的第一个参数对应   handle 为对应的事件处理函数	 			--> 			<menu-item :parr='parr' @enlarge-text='handle($event)' @shrink-text='shrink($event)' 				@gain-num="gainNume($event)" 			></menu-item> 		</div> 		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script> 		<script type="text/javascript"> 			/* 		      子组件向父组件传值-携带参数 		    */  			Vue.component('menu-item', { 				props: ['parr'], 	/* 			data: function() { 					return { 						num: 0, 					} 				}, */ 				template: ` 		        <div> 		          <ul> 		            <li :key='index' v-for='(item,index) in parr'>{{item}}</li> 		          </ul> 					###  1、子组件用$emit()触发事件 					### 第一个参数为 自定义的事件名称   第二个参数为需要传递的数据   					<br> 				  <button @click='$emit("gain-num", 0)'>将子组件的值传递到父组件</button> 		          <button @click='$emit("enlarge-text", 5)'>扩大父组件中字体大小</button> 		          <button @click='$emit("shrink-text", 5)'>缩小父组件中字体大小</button> 		        </div> 		      ` 			}); 			var vm = new Vue({ 				el: '#app', 				data: { 					pmsg: '父组件中内容', 					parr: ['apple', 'orange', 'banana'], 					fontSize: 10 				}, 				methods: { 					handle: function(val) { 						alert(val) 						// 扩大字体大小 						this.fontSize += val; 					}, 					shrink: function(val) { 						// 缩小 字体大小 						this.fontSize -= val; 					}, 					gainNume: function(val){ 						alert(val); 					} 				} 			}); 		</script> 	</body> 

3.兄弟组件的传递

  • 兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据
    • 提供事件中心 var hub = new Vue()
  • 传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)
  • 接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
  • 销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据

​ 简单想象一下兄弟组件,在全局组件中,定义两个全局组件那么这两个组件就是兄弟组件,局部组件也是一样的,在当个实例中可定义多个局部组件组件之间就是兄弟关系

<body> 		<div id="app"> 			<div>父组件</div> 			<div> 				<button @click='handle'>销毁事件</button> 			</div> 			<test-tom></test-tom> 			<test-jerry></test-jerry> 		</div> 		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script> 		<script type="text/javascript"> 			/* 		      兄弟组件之间数据传递 		    */ 			//1、 提供事件中心 			var hub = new Vue();  			Vue.component('test-tom', { 				data: function() { 					return { 						num: 0 					} 				}, 				template: ` 		        <div> 		          <div>TOM:{{num}}</div> 		          <div> 		            <button @click='handle'>点击</button> 		          </div> 		        </div> 		      `, 				methods: { 					handle: function() { 						//2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件 						hub.$emit('jerry-event', 2); 					} 				}, 				mounted: function() { 					// 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on(方法名 					hub.$on('tom-event', (val) => { 						this.num += val; 					}); 				} 			}); 			Vue.component('test-jerry', { 				data: function() { 					return { 						num: 0 					} 				}, 				template: ` 		        <div> 		          <div>JERRY:{{num}}</div> 		          <div> 		            <button @click='handle'>点击</button> 		          </div> 		        </div> 		      `, 				methods: { 					handle: function() { 						//2、传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)   触发兄弟组件的事件 						hub.$emit('tom-event', 1); 					} 				}, 				mounted: function() { 					// 3、接收数据方,通过mounted(){} 钩子中  触发hub.$on()方法名 					hub.$on('jerry-event', (val) => { 						this.num += val; 					}); 				} 			}); 			 			var vm = new Vue({ 				el: '#app', 				data: {  				}, 				methods: { 					handle: function() { 						//4、销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据   						hub.$off('tom-event'); 						hub.$off('jerry-event'); 					} 				} 			}); 		</script> 	</body> 

4.自定义轮播组件

四.组件插槽

  • 组件的最大特性就是复用性,而用好插槽能大大提高组件的可复用能力
  • 插槽标签指令
  • 在组件的基础上的一个标签

1.匿名插槽

  • 没有被name属性修饰的插槽叫匿名插槽
<body> 		<div id="app"> 			<alert-box></alert-box> 			<!-- 插槽相当于一个默认的占位符,如果没有去修饰值修饰它,它会以默认值展现 --> 			<alert-box1>{{msg}}</alert-box1> 		</div>  		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script> 		<script type="text/javascript"> 			Vue.component("alert-box", { 				template: ` 					<div> 						###在组件模板中定一个插槽<br> 						<slot>我是一个插槽</slot> 					</div> 				` 			}) 			Vue.component("alert-box1", { 				template: ` 					<div> 						###虽然我也是一个插槽但是我被占用了<br> 						<slot>我是一个插槽</slot> 					</div> 				` 			})  			new Vue({ 				el: "#app", 				data: { 					msg:"我要占用插槽" 				} 			}); 		</script> 	</body> 

2.具名插槽

  • 被name属性修饰的插槽叫具名插槽
<body> 		<div id="app"> 			<table-list> 				<!-- 在模板内分别为两个插槽填充数据 达到表格效果 --> 				<template slot='heands'> 					<th>ID</th> 					<th>名称</th> 				</template> 				<template slot='tablebody'> 					<tr v-for="l in list"> 						<td>{{l.id}}</td> 						<td>{{l.name}}</td> 					</tr> 				</template> 			</table-list> 		</div> 		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script> 		<script type="text/javascript"> 			// 定义一个 table 组件  			Vue.component('table-list', { 				// 定义一个模板,定义两个插槽heands 和 tablebody 				template: ` 					<table> 						<tr> 						   <slot name='heands'></slot>  						</tr> 						<tbody> 							 <slot name="tablebody"></slot>  						</tbody> 					</table> 				` 			}) 			new Vue({ 				el: "#app", 				data: { 					// 准备初始化数据 					list: [{ 							id: 1, 							name: '李四' 						}, 						{ 							id: 2, 							name: '张三' 						}, 						{ 							id: 3, 							name: '张飞' 						}, 					] 				} 			}) 		</script> 	</body> 

3.作用域插槽

  • 父组件对子组件加工处理
  • 既可以复用子组件的slot,又可以使slot内容不一致
<body> 		<div id="app"> 			<!--  				1、当我们希望li 的样式由外部使用组件的地方定义,因为可能有多种地方要使用该组件, 				但样式希望不一样 这个时候我们需要使用作用域插槽  				 			--> 			<fruit-list :list='list'> 				<!-- 2、 父组件中使用了<template>元素,而且包含scope="slotProps", 					slotProps在这里只是临时变量    				---> 				<template slot-scope='slotProps'> 					<strong v-if='slotProps.info.id==3' class="current"> 						{{slotProps.info.name}} 					</strong> 					<span v-else>{{slotProps.info.name}}</span> 				</template> 			</fruit-list> 		</div> 		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script> 		<script type="text/javascript"> 			/* 		      作用域插槽 		    */ 			Vue.component('fruit-list', { 				props: ['list'], 				/*  				   3、 在子组件模板中,<slot>元素上有一个类似props传递数据给组件的写法msg="xxx", 				   插槽可以提供一个默认内容,如果如果父组件没有为这个插槽提供了内容,会显示默认的内容。 				 		如果父组件为这个插槽提供了内容,则默认的内容会被替换掉 				 */ 				template: ` 		        <div> 		          <li :key='item.id' v-for='item in list'> 		            <slot :info='item'>{{item.name}}</slot> 		          </li> 		        </div> 		      ` 			}); 			var vm = new Vue({ 				el: '#app', 				data: { 					list: [{ 						id: 1, 						name: 'apple' 					}, { 						id: 2, 						name: 'orange' 					}, { 						id: 3, 						name: 'banana' 					}] 				} 			}); 		</script> 	</body> 

五.习题练习

1.定义轮播组件

<html> 	<head> 		<meta charset="utf-8"> 		<title>自定义轮播组件</title> 	</head> 	<style type="text/css"> 		* { 			padding: 0; 			margin: 0; 		}  		#slideshow { 			width: 800px; 			height: 400px; 			margin: 50px auto; 			border: 1px solid black; 			position: relative; 		} 	</style> 	<body> 		<div id="slideshow"> 			<slides-show :images="images" :index="index" :img-style="imgStyle" :show="show" :hide="hide" :prepagestye="prepagestye" 			 :nextpagestye="nextpagestye" :slideshowstyle="slideshowstyle" 			 @prepage='prepage' @nextpage='nextpage' 			 >  			</slides-show> 		</div>  		<script src="js/vue.js" type="text/javascript" charset="utf-8"></script> 		<script type="text/javascript"> 			Vue.component("slides-show", { 				props: ['images', 'index', 'slideshowstyle', 'imgStyle', 'show', 'hide', 					'prepagestye', 'nextpagestye' 				], 				template: ` 				<div :style="[slideshowstyle]"> 					<ul v-for="imgs in images" class="slideTable"> 						<li v-if="index === imgs.id" :style="[show]"> 							<img :src="imgs.path" :style="[imgStyle]"> 						</li> 						<li v-else :style="[hide]"> 							<img :src="imgs.path" > 						</li> 					</ul> 					<span :style="[prepagestye]" @click='$emit("prepage")'> 						<img src = "img/上一页.png" alt="vue组件学习" width="30px"> 					</span> 					<span :style="[nextpagestye]"  @click='$emit("nextpage")'> 						<img src = "img/上一页.png" width="30px"> 					</span> 				</div> 				` 			}) 			new Vue({ 				// 绑定父元素 				el: "#slideshow", 				data: { 					// 定义轮播图片数据 					index: 1, 					images: [{ 							id: 1, 							path: 'img/3Dbg01.jpg' 						}, 						{ 							id: 2, 							path: 'img/3Dbg02.jpg' 						}, 						{ 							id: 3, 							path: 'img/3Dbg03.jpg' 						}, 						{ 							id: 4, 							path: 'img/3Dbg04.jpg' 						} 					], 					// 隐藏显现 					hide: { 						display: 'none' 					}, 					show: { 						display: 'inline' 					}, 					//定义图片大小 					imgStyle: { 						width: '100%', 						height: '400px' 					}, 					// 上一页 					prepagestye: { 						width: '35px', 						position: 'absolute', 						top: '190px', 						display: 'inline-block', 						left: '8px', 						cursor: 'pointer', 					}, 					// 下一页 					nextpagestye: { 						width: '35px', 						position: 'absolute', 						top: '190px', 						right: '8px', 						cursor: 'pointer', 						display: 'inline-block', 						transform: ' rotate(180deg)', 					}, 					slideshowstyle: { 						position: 'relative', 						width: '100%', 						height: '400px' 					} 				}, 				methods: { 					prepage: function() { 						console.log('11') 						if(this.index <= 1){ 							this.index = this.images.length; 						}else{ 							this.index = this.index-1; 						} 					}, 					nextpage: function() { 						console.log('2') 						if (this.index >= this.images.length) { 							this.index = 1; 						} else { 							this.index = this.index + 1; 						} 					} 				} 			}) 		</script> 		<!-- <style type="text/css"> 			.slideshowStyele { 				position: relative; 				text-align: center; 				background-color: #fff; 				list-style: none; 				width: 100%; 				height: auto; 				width: 30px; 				height: 30px; 				transform: rotate(180deg); 				cursor: pointer; 				display: inline-block; 			} 		</style> --> 	</body> </html> 

2.dmoe下载

https://gitee.com/li_shang_shan/vue-component-learning-dmeo