Vue入门四(组件介绍与定义|组件之间的通信|动态组件|slot插槽)

文章目录

  • 一、组件介绍与定义
    • 介绍
    • 定义
      • 1)全局组件
      • 2)局部组件
  • 二、组件之间的通信
    • 1)父组件向子组件传递数据
    • 2)子传父通信
    • 3)Ref属性(也可以实现组件间通信:子和父都可以实现通信)
  • 三、动态组件
    • 通过条件渲染和动态组件实现切换
    • 缓存组件
  • 四、slot插槽
    • 匿名插槽
    • 具名插槽

一、组件介绍与定义

介绍

组件(Component)是Vue.js 最强大的功能之一,它是html、css、js等的一个聚合体。封装性和隔离性非常强。

组件相当于Python中的模块,扩展HTML元素,可以重复使用的代码,使用它就是为了重复使用

例如:一个轮播图需要使用放到很多页面当中使用,一个轮播图有它自己的js、css、html,而组件就可以快捷的做出一个轮播图,有自己的js、css、html放到一起,有自己的逻辑样式这样到哪里都可以使用了,无需在写重复代码

  • 模块化: 是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一;
  • 组件化: 是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用。

定义

1)全局组件

语法

Vue.component('组件名称', { }),第1个参数是标签名称,第2个参数是一个选项对象。全局组件注册后,任何vue实例都可以用。

组件注意事项:

  1. 构造Vue实例时,传入的各种选项大多数可以在组件里使用(el不能使用),只有一个例外:data必须是函数,同时这个函数要求返回一个对象,保证数据唯一性,防止对象发生污染。
  2. 组件模版必须是单个根元素(html标签)
  3. 组件模版的内容可以是模版字符串
	DOCTYPE html>
	<html lang="en">
	<head>
	    <meta charset="UTF-8">
	    <title>Documenttitle>
	    <script src="../js/vue.js">script>
	head>
	
	<body>
	
	    <div id="div">
	        <h1>组件h1>
	        <button @click="handleClick">点击显示button>
	        <hr>
	        
	        <child v-if="show==true">child>
	    div>    
	
	    <script>
	        var vm = new Vue({
	            el:'#div',
	            data:{
	               show:false,
	            },
	            methods:{
	                handleClick(){
	                    this.show=!this.show
	                }
	            }
	        })
	
			//定义全局组件,一次定义,随时使用
			//一但声明完成,就可以在所有的组件中直接使用,无需引入和注册
	        Vue.component('Child',{
	        	//模版字符串
	            template:`
	                

{{name}}

`
, /* render 用于直接生成虚拟dom(生成标签) 在工程化中,render中可以直接写jsx,在引入一个babel可以写jsx语法(js的增强版本) render(h) { // h(生成的标签名称,标签中有哪些属性(没有属性就是null),子元素是什么) let vnode = h('h3', { attrs: { name: 'abc', style: 'color:red' } }, '我是一个标签') return vnode } */ data(){ return{ name:'tom', } }, methods:{ clickname(){ this.name='jack' } } })
script> body> html>

没有使用工程化时,我们使用浏览器解析标签,所以定义组件不能使用单标签写法,否则组件无法被多次执行,因为但标签写法浏览器在解析的时候觉得有问题,不再执行后续标签。而在工程化中,我们会使用很多包来编译html标签,单标签写法是允许的


2)局部组件

单文件局部组件

	DOCTYPE html>
	<html lang="en">
	<head>
	    <meta charset="UTF-8">
	    <title>Documenttitle>
	    <script src="../js/vue.js">script>
	head>
	<body>
	
	    <div id="div">
	        <child>child>
	    div>    
	
	    <script>
	    	// 创建局部组件,它就是一个对象
		    // 局部组件,在创建完成后,如果你要给别人使用,一定要在配置中进行对应的配置
	        var child = {
	            template:`
	            

自定的局部组件

{{title}}
`
, data(){ return { title:'hello world', } } } var vm = new Vue({ el:'#div', data:{ }, methods:{ }, //局部组件要加s components:{ // key就是在使用时的标签名称 // value就是对应的局部组件对象 // child: child // 简写 child } })
script> body> html>

直接编写在组件内部的局部组件

	DOCTYPE html>
	<html lang="en">
	<head>
	    <meta charset="UTF-8">
	    <title>Documenttitle>
	    <script src="../js/vue.js">script>
	head>
	<body>
	    <div id="div">
	        <child>child>
	        <hr>
	        
	        <mcq>mcq>
	    div>    
	
	    <script>
	        var child = {
	            template:`
	            

自定的局部组件

{{title}}
`
, data(){ return { title:'hello world', } } } //注意根组件与全局组件无任何关联关系,需放在根组件前面执行,否则会报错 //在全局组件内定义局部组件 Vue.component('mcq',{ template:`

我是全局组件

`
, data(){ return {} }, //在内部定义的局部组件 components:{ xxx:{ template:`

我是局部组件

`
, data(){ return {} }, } } }) var vm = new Vue({ el:'#div', data:{ }, methods:{ }, //局部组件要加s components:{ // key就是在使用时的标签名称 // value就是对应的局部组件对象 // child: child // 简写 child } })
script> body> html>

二、组件之间的通信

组件与组件之间的嵌套使用避免不了数据之间的传递。那么Vue中组件的数据是如何传递的呢?组件间数据传递不同于Vue全局的数据传递,组件实例的数据之间是孤立的,不能在子组件的模板内直接引用父组件的数据。如果要把数据从父组件传递到子组件,就需要使用props属性。在Vue中,父子组件的关系可以总结为:prop向下传递,事件向上传递。父组件通过prop给子组件下发数据,子组件通过事件给父组件发送消息。所以我们也会经常遇到组件之间需要传递数据的时候,大致分为四种情况:

  • 父组件向子组件传递数据,通过props传递数据
  • 子组件向父组件传递数据,通过events(自定义事件—回调函数)传递数据。
  • 两个同级组件(兄弟组件)之间传递数据,通过EventBus(事件总线–只适用于极小的项目)、Vuex(官方推荐)传递数据
  • 其他方式通信-处理边界情况:
    • 1) $parent:父实例,如果当前实例有的话。通过访问父实例也能进行数据之间的交互,但极少情况下会直接修改父组件中的数据。
    • 2)$root:当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
    • 3)$children:当前实例的直接子组件。需要注意 $children 并不保证顺序,也不是响应式的。如果你发现自己正在尝试使用 $children 来进行数据绑定,考虑使用一个数组配合 v-for 来生成子组件,并且使用 Array 作为真正的来源。
    • 4) $ref:一个对象,持有注册过 ref attribute 的所有 DOM 元素和组件实例。访问子组件实例或子元素
    • 5) provide / inject。主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。并且这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。

1)父组件向子组件传递数据

	1.通过自定义属性---自定义的变量不能用驼峰,不要与子组件中的变量冲突
   		父组件里 <child :name"name" ></child>
	2. 子组件中引用props,可以指定自定义属性的类型,也可以直接用数组
	    props:{name:String} // props:['name'] /可以接收多个
	DOCTYPE html>
	<html lang="en">
	<head>
	    <meta charset="UTF-8">
	    <title>Documenttitle>
	    <script src="../js/vue.js">script>
	head>
	<body>
	    <div id="div">
	        <h1>组件通信之父传子:自定义属性h1>
	        父组件中的名字:{{name}}
	        <hr>
	        <global :name="name">global>
	    div>    
	
	    <script>
	        
	        Vue.component('global',{
	            template:`
	                

我是global组件

父组件传递给子组件的:{{name}}

`
, data(){ return{} }, props:['name',] }) var vm = new Vue({ el:'#div', data:{ name:'jack', }, methods:{}, })
script> body> html>

Vue入门四(组件介绍与定义|组件之间的通信|动态组件|slot插槽)_第1张图片


2)子传父通信

上面说到父组件向子组件传值,子组件处理完这些数据后可能会返回给父组件一些数据,此时就不能使用props 来处理了而是通过自定义事件来实现。因为子组件什么时候处理完数据是不确定的,因此需要捕获子组件触发的事件得知子组件的状态。简单说就是当子组件处理数据完成之后触发事件,父组件通过$emit(‘事件名称’)捕获子组件发出的事件进行相应的处理即可。

	DOCTYPE html>
	<html lang="en">
	<head>
	    <meta charset="UTF-8">
	    <title>Documenttitle>
	    <script src="../js/vue.js">script>
	head>
	<body>
	    <div id="div">
	        <h1>组件通信之子传父:自定义事件h1>
	        父组件接收的名字:{{p_name}}
	        <hr>
	        <global @myevent="handleEvent">global>
	    div>    
	
	    <script>
	        Vue.component('global',{
	            template:`
	                

我是global组件

`
, data(){ return{ name:'jack', } }, methods:{ handleSend(){ this.$emit('myevent',this.name) } } }) var vm = new Vue({ el:'#div', data:{ p_name:'', }, methods:{ handleEvent(name){ console.log('接收到了子组件传递的名字:'+name) this.p_name=name } }, })
script> body> html>

Vue入门四(组件介绍与定义|组件之间的通信|动态组件|slot插槽)_第2张图片


3)Ref属性(也可以实现组件间通信:子和父都可以实现通信)

ref被用来给元素或组组件注册引用信息。引用信息将会被注册在父组件的$refs对象上。如果在普通的DOM元素上使用,引用指向的就是DOM元素;如果用在子组件上,引用就指向组件实例

  • ref放在标签上,拿到的是原生的DOM节点
  • ref放在组件上,拿到的是组件对象 ,对象中的数据、函数 都可以直接使用
  • 通过这种方式实现子传父(this.$refs.mychild.name)
  • 通过这种方式实现父传子(调用子组件方法传参数)
	DOCTYPE html>
	<html lang="en">
	<head>
	    <meta charset="UTF-8">
	    <title>Documenttitle>
	    <script src="../js/vue.js">script>
	head>
	<body>
	    <div id="div">
	        <h1>ref属性放在普通标签上h1>
	        名字:<input type="text" v-model="name" ref="myinput">
	        <p/>
	        <button @click="handlePrint">点击传递button>
	        <p/>
	        
	        <hr>
	        <h1>ref属性放在组件上h1>
	        <child ref="mychild">child>
	    div>    
	
	    <script>
	        
	        Vue.component('Child',{
	            template:`
	            

名字:{{name}}
年龄:{{age}}

`
, data(){ return { name:'jack', age:'19' } }, methods:{ handleClick(){ alert('名字为:'+this.name+','+'年龄为:'+this.age) } } }) var vm = new Vue({ el:'#div', data:{ name:'', }, methods:{ handlePrint(){ // 1 ref 属性放在普通标签上,拿到标签的dom对象 /* 通过this.$refs可以拿到所有标签上写了ref属性的 标签 , 对象类型 key值是ref对应的value值, value值是原生dom对象 */ // console.log(this.$refs) var i = this.$refs['myinput'] console.log(i) // 直接修改原生dom对象的value属性,input就能看到有值了 i.value = 'hello world' //2ref 属性放在 组件上,拿到的是 组件对象 ,就可以使用组件对象的属性和方法 console.log(this.$refs) //此时可以看到对象中有两个值,一个是普通对象,一个是组件对象 var n = this.$refs['mychild'] // 就是组件对象,可以 .属性, .方法 console.log(n._data.name) //可以通过_data拿到属性 console.log(n.age) //也可以省略_data拿到属性,和根组件是一样的 n._data.name='tom' n.age='66' // 重点:以后就不需要关注是子传父还是父传子了,直接通过对象取值赋值即可,而且可以主动调用子组件中的函数 setTimeout(()=>{ n.handleClick() //触发子组件的点击事件执行 },2000) // 把子组件属性传递给父组件 this.name=n.name } }, })
script> body> html>

Vue入门四(组件介绍与定义|组件之间的通信|动态组件|slot插槽)_第3张图片


三、动态组件

通过条件渲染和动态组件实现切换

	DOCTYPE html>
	<html lang="en">
	<head>
	    <meta charset="UTF-8">
	    <title>Documenttitle>
	    <script src="../js/vue.js">script>
	head>
	
	<body>
	
	    <div id="div">
	        <h1>使用条件渲染实现切换h1>
	
	        <button @click="who='index'">首页button>  
	        <button @click="who='goods'">商品页button>  
	        <button @click="who='order'">订单页button>
	        <hr>
	
	        
	        <Home v-if="who=='index'">Home>
	        <Goods v-else-if="who=='goods'">Goods>
	        <Order v-else>Order>
	        <p/>
	        <hr>
	
	        <h1>使用动态组件实现切换h1>
	        
	        <button @click="type='login'">登录button>  
	        <button @click="type='register'">注册button>  
	        <hr>
	        <component :is="type">component>
	    div>    
	
	    <script>
	
	        var vm = new Vue({
	            el:'#div',
	            data:{
	                who:'index',
	                type:'',
	            },
	            methods:{
	
	            },
	            components:{
	                Home:{
	                    template:`
	                    

首页

`
, }, Goods:{ template:`

商品页

`
, }, Order:{ template:`

订单页

`
, }, Login:{ template:`

登录页

`
, }, Register:{ template:`

注册页

`
, data(){ return{ search:'' } }, }, } })
script> body> html>

Vue入门四(组件介绍与定义|组件之间的通信|动态组件|slot插槽)_第4张图片

从上述代码中,当我们在注册组件中的input框中写入数据,我们可以发现一旦切换重新切回来数据就丢失了。这个是因为每一次的切换都会销毁切换前的组件,所以数据就没有了。这时我们就需要用到缓存组件


缓存组件

	DOCTYPE html>
	<html lang="en">
	<head>
	    <meta charset="UTF-8">
	    <title>Documenttitle>
	    <script src="../js/vue.js">script>
	head>
	
	<body>
	
	    <div id="div">
	        <h1>使用动态组件实现切换h1>
	        
	        <button @click="type='login'">登录button>  
	        <button @click="type='register'">注册button>  
	        <hr>
	        
	        
	        <keep-alive>
	            <component :is="type">component>
	        keep-alive>
	         
	        
	    div>    
	
	    <script>
	
	        var vm = new Vue({
	            el:'#div',
	            data:{
	                type:'',
	            },
	            components:{
	                Login:{
	                    template:`
	                    

登录页

`
, destroyed(){ //销毁之后执行, console.log('执行销毁login了') } }, Register:{ template:`

注册页

`
, data(){ return{ search:'' } }, destroyed(){ //销毁之后执行, console.log('执行销毁register了') } }, }, })
script> body> html>

Vue入门四(组件介绍与定义|组件之间的通信|动态组件|slot插槽)_第5张图片


注意:

  1. 缓存组件中的元素并没有被销毁,而是处于未被激活状态,tab 切换时的性能得到很大的提升
  2. 缓存组件带来的问题是,组件的初始化阶段生命周期函数只执行一次
    Vue 提供了 activated 和 deactivated 两个生命周期钩子函数用来判断组件处于激活状态还是未激活状态。在缓存组件中,虽然初始化生命周期钩子函数只在创建组件时执行一次,但是如果有了这两个钩子函数,我们的代码就可以通过这两个函数进行捕获
  • activated 和 deactivated 只有标签中有keep-alive才有用
  • keep-alive 标签有两个属性,include 和 exclude。

四、slot插槽

  • 一般情况下,编写完1个组件之后,组件的内容都是写死的,需要加数据 只能去组件中修改,扩展性很差
  • 然后就出现了插槽这个概念,只需在组件中添加,就可以在组件标签中添加内容

匿名插槽

匿名语法: 或者(default可省略)

	DOCTYPE html>
	<html lang="en">
	<head>
	    <meta charset="UTF-8">
	    <title>Documenttitle>
	    <script src="../js/vue.js">script>
	head>
	
	<body>
	
	    <div id="div">
	        <h1>slot插槽的使用h1>
	        <hr>
	
	        <Home>
	            <input type="text" placeholder="请输入">
	            <button>插入的按钮button>
	        Home>
	        
	
	        
	    div>    
	
	    <script>
	
	        var vm = new Vue({
	            el:'#div',
	            data:{
	                
	            },
	            components:{
	                Home:{
	                    //定义个插槽,就相当于是电脑上的预留的插槽,
	                    //或者是模版语法的block
	                    template:`
	                    

这里是局部组件Home

局部组件结尾处

`
, } }, })
script> body> html>

Vue入门四(组件介绍与定义|组件之间的通信|动态组件|slot插槽)_第6张图片

从上述案例上可以看到,当子组件内的slot不止一个,那么我们如何在父组件中,精确的在想要的位置,插入对应的内容呢?那就取个名字,一个不带 name 的 出口会带有隐含的名字“default”。


具名插槽

所谓的具名插槽就是为每个插槽取个具体的名字。对传递内容进行唯一性的标识。

具名语法:

具名缩写: