1.1全局组件注册:Vue.component('didi-component',DIDIComponent)html
参数1('didi-component'):注册组件的名称,即在HTML中可使用对应名称的自定义标签来添加组件:<didi-component></didi-component>,名称除了使用中划线与html中添加自定义标签一致之外,还可使用小驼峰命名方式来定义名称,一样vue内部会自动匹配到中划线的html自定义标签上,即‘didi-component’同等于‘didiComponent’,也有不规范的写法直接自定义任意英文字符,不采用链接符(中划线)也不采用小驼峰命名,也是能够的,后面示例中会有出现。vue
参数2(DIDIComponent):注册组件的钩构造函数Function,也能够是Object。数组
1 //组件构造器构造组件: 2 var MyComponent = Vue.extent({ 3 //选项... 4 }) 5 //传入选项对象注册全局组件: 6 Vue.component('didi-component',{ 7 template:`<div>A custom component!</div>` 8 })
实例代码(代码折叠):浏览器
1 <div id="example"> 2 <didi-component></didi-component> 3 </div> 4 <script> 5 var DIDIComponent = Vue.extend({ 6 template:`<div>A custom component!</div>` 7 }) 8 //注册 9 Vue.component('didi-component',DIDIComponent) 10 //建立vue根实例 11 new Vue({ 12 el:'#example' 13 }) 14 </script>
1.2局部组件注册:也能够说是在vue实例上注册组件。缓存
1 <div id="example"> 2 <didi-component></didi-component> 3 </div> 4 <script> 5 //注册局部组件 6 var DIDIComponent = Vue.extend({ 7 template:`<div>i am child!</div>` 8 }); 9 new Vue({ 10 el:'#example', 11 components:{ 12 didiComponent:DIDIComponent 13 } 14 }); 15 </script>
所谓局部组件就是不使用Vue.component()注册组件,而是直接在vue实例上,经过component添加组件,上面的示例中将组件构造写在实例外面,也能够直接写在实例中:app
1 new Vue({ 2 el:'#example', 3 components:{ 4 didiComponent:Vue.extend({ 5 template:`<div>i am child!</div>` 6 }) 7 } 8 });
注:如今的vue中构造组件能够不用写Vue.extend()方法,而是能够直接写成对象形式,后面的示例中将所有省略Vue.extend()。ide
全局组件与局部组件除了全局组件经过Vue.component()方法注册,而局部组件直接经过component添加到vue实例对象上之外。全局组件注册会始终以构造的形式被缓存,而局部组件不被使用时,不会被构造缓存,而是在vue实例须要时才被构造。虽说将组件构造缓存在内存中能够提升代码执行效率,可是另外一方面是消耗大量的内存资源。函数
除了上面的单个构造注册,也能够直接在模板中使用引用其余局部组件:测试
1 <div id="example"> 2 <didi-component></didi-component> 3 </div> 4 <script> 5 var Child = { 6 template:`<div>i am child!</div>`, 7 replace:false 8 } 9 var Parent = { 10 template:`<div>//这个包装元素如今不能使用template标签了,之前能够 11 <p>i am parent</p> 12 <br/> 13 <child-component></child-component> 14 </div>`, 15 components:{ 16 'childComponent':Child//在局部组件模板中直接引用其余局部组件 17 } 18 } 19 new Vue({ 20 el:'#example', 21 components:{ 22 didiComponent:Parent 23 } 24 }) 25 </script>
2.1经过props实现数据传输:优化
经过v-bind在组件自定义标签上添加HTML特性创建数据传递通道;
在组件字段props上绑定自定义标签上传递过来的数据;
1 <div id="example"> 2 <!--将vue实例中的数据resultsList绑定到自定义特性list上--> 3 <didi-component :list="resultsList"></didi-component> 4 </div> 5 <script> 6 //绑定数据 7 var Child = { 8 props:['list'],//经过自定义特性list获取父组件上的resultsList 9 template: `<div> 10 <p> 11 <span>姓名</span> 12 <span>语文</span> 13 <span>数学</span> 14 <span>英语</span> 15 </p> 16 <ul> 17 <li v-for="item in list" :key="item.name"> 18 <span>{{item.name}}</span> 19 <span>{{item.results.language}}</span> 20 <span>{{item.results.math}}</span> 21 <span>{{item.results.english}}</span> 22 </li> 23 </ul> 24 </div>` 25 } 26 var vm = new Vue({ 27 el:'#example', 28 components:{ 29 didiComponent:Child 30 }, 31 data:{ 32 resultsList:[ 33 { 34 name:"张三", 35 results:{language:89,math:95,english:90} 36 }, 37 { 38 name:"李四", 39 results:{language:92,math:76,english:80} 40 }, 41 { 42 name:"王五", 43 results:{language:72,math:86,english:98} 44 } 45 ] 46 } 47 }); 48 </script>
经过上面的示例能够了解到子组件获取父组件上的数据过程,这你必定会有一个疑问,props这里这有什么做用,为何须要props这个字段?
a.每一个组件都有本身独立的实例模型来管理本身身的属性,经过props获取到父组件中自身须要的数据,并使用自身属性缓存数据引用能够提升程序执行效率,若是将父组件上的数据全盘接收过来,对于子组件自身来讲会有大量多余的数据,反而下降程序执行效率。
b.经过props数据校验,保证获取到正确无误的数据,提升程序的可靠性。
2.2props接收数据的方式及校验处理:
1 //采用数组形式接收父组件经过自定义特性传递过来的数据 2 props:['data1','data2','data3'...]//数组接收方式只负责数据接收,并不对数据校验处理 3 4 //采用对象形式接收父组件经过自定义特性传递过来的数据 5 props:{ 6 data1:{ 7 type:Array,//校验接收数据类型为Array,type的值还能够是数组,添加数据可符合多种数据类型的校验 8 default:[{name:"我是默认参数",...}],//当data1没有接收到数据时,组件就会使用默认数据渲染 9 required:true,//校验必须接收到数据,否则即便在有默认数据的状况下也会在控制台打印出报错提示 10 validator(value){//校验数据具体信息,参数value接收到的是传递过来的数据,若是方法返回false则表示数据不符合条件,控制台打印出报错提示 11 return value.length < 1; 12 } 13 }
采用数据校验只能保证在数据有误时在控制台打印出相对准确的错误提示,并不会阻止数据渲染,也不会阻塞后面的代码执行。
2.3栈内存传值与单向数据流
1 <div id="example"> 2 父级对象传值:<input type="text" v-model="info.name"/> 3 父级对象属性传值:<input type="text" v-model="obj.name"> 4 父级字符串传值:<input type="text" v-model="name"> 5 <child v-bind:msg1.sync="info" v-bind:msg2="obj.name" v-bind:msg3="name"></child> 6 </div> 7 <script> 8 new Vue({ 9 el:'#example', 10 data:{ 11 info:{ 12 name:'顺风车' 13 }, 14 obj:{ 15 name:"专车" 16 }, 17 name:"快车" 18 }, 19 components:{ 20 'child':{ 21 props:['msg1','msg2','msg3'], 22 template:` <div> 23 接收对象传值:<input type="text" v-model="msg1.name"/> 24 接收对象属性传值:<input type="text" v-model="msg2"> 25 接收字符串传值:<input type="text" v-model="msg3"> 26 </div>` 27 } 28 } 29 }) 30 </script>
示例效果:
经过示例能够看到vue父子组件传值,采用的是父级向子级的单向数据流,当父级数据发生变化时,子级数据会跟着变化。但同时又由于传递值的方式采用的是栈内存赋值方式,若是父级传递给子级的是引用值类型数据,在子级中改变数据也会引起父级数据的更改。
注:在权威指南中有说能够经过给数据传递添加修饰符once和sync来控制数据的但双向绑定,在2.x中测试无效,但添加这两个修饰符不报错。关于个问题我想应该是版本更新优化的,但不能肯定,毕竟我没有使用过以前的版本,也没有阅读以前版本的手册和源码,这个一点暂时保留意见。
因为vue父子组件传值采用了栈内存传值,就没有办法保证数据的单向传递,解决这个问题的办法很简单,在子组件中经过克隆该数据用自身的数据引用保存下来就OK了,但要注意在子组件中声明data须要使用function类型。
注:即使传递的数据是原始值类型的数据,也不要直接使用接收的数据,虽然程序能正常运行,可是vue在控制台对直接使用原始值的行为报警告,因此正确的数据接收方式是经过props接收后在子组件自身的数据上再建立一个数据副原本使用。
data(){ return{ //自身数据引用名称:接收父级传递来的引用数据的克隆数据 } }
在上一节介绍了父子组件数据传递,咱们知道vue采用的是单向数据流,这就意味着当子组件须要向父组件传值时就得须要其余手段,毕竟在更多时候子组件极可能会有复用性状况,因此子组件也不能直接去更改父组件的数据,但总会有不少需求须要咱们将子组件的计算结果传递给父组件。
Vue经过在父级做用域上定义事件,而后再由子组件使用$emit来实现事件派送,当派送到对应事件做用域时,使用派送过来的参数(数据)触发事件回调函数,这就是组件通讯。经过组件通讯能够解决子组件向父组件传值,示例:
1 <div id="example"> 2 <!-- 经过transmitdata接收来自子组件的传输指令,而后触发父组件的addComData方法 --> 3 <comment-complate @transmitdata="addComData"></comment-complate> 4 <ul> 5 <li v-for="item in comList" :key="item.id"> 6 <div> 7 <span class="userName">{{item.userName}}</span> 8 <span class="comDate">{{item.date}}</span> 9 </div> 10 <div>{{item.val}}<div> 11 </li> 12 </ul> 13 </div> 14 <script> 15 var comment = { 16 template: `<div> 17 <textarea v-model="comVal"></textarea> 18 <p><button @click="submitData">提交</button></p> 19 </div>`, 20 data(){ 21 return { 22 userId:1001, 23 userName:"他乡踏雪", 24 comVal:"" 25 } 26 }, 27 methods:{ 28 submitData(){ 29 console.log(this.comVal + 'a'); 30 var comDate = new Date(); 31 let comData = { 32 id:Number(''+this.userId + comDate.getTime()), 33 userName:this.userName, 34 date:comDate.toLocaleDateString() + ' ' + comDate.toLocaleTimeString(), 35 val:this.comVal 36 } 37 this.$emit('transmitdata',comData);//经过$emit监听提交留言的点击事件,被触发后将数据传递给自定义方法transmitdata 38 } 39 } 40 } 41 var vm = new Vue({ 42 el:'#example', 43 components:{ 44 commentComplate:comment 45 }, 46 data:{ 47 comList:[ 48 { 49 userName:"南都谷主", 50 id:1001, 51 date:'2019/7/8 上午00:32:55', 52 val:'2017年,在长春园东南隅的如园遗址,工做人员在进行' 53 } 54 ] 55 }, 56 methods:{ 57 addComData(data){ 58 //transmitdata自定义事件接收到数据传递请求后,将传递过来的数据交给addComData处理 59 this.comList.unshift(data); 60 } 61 } 62 }); 63 </script>
若是用来接收数据传输数据的事件是一个原生的DOM事件,就没必要使用$emit()来监听事件触发,只须要在原生的事件声明后添加一个‘.navite’后缀就能够自动实现监听事件触发,原生事件自己就是由浏览器自身监听,因此没必要要多余的操做。如:@click.navite="父组件的事件监听方法"。
<comment-complate @click="addComData"></comment-complate>
4.1插槽:<slot></slot>
有时候须要在父级组件中给子组件添加一些节点内容,这时候就能够在使用slot来实现,而且还能够在子组件中使用具名插槽,来实现指定位置插入节点。示例:
1 <div id="app"> 2 <child> 3 <span>{{titleName}}:</span><!--插入插槽--> 4 </child> 5 </div> 6 <script> 7 var vm = new Vue({ 8 el:'#app', 9 components:{ 10 child:{ 11 template:` <div> 12 <slot></slot><!--定义插槽--> 13 <input type="text" /> 14 </div>` 15 } 16 }, 17 data:{ 18 titleName:'邮箱' 19 } 20 }); 21 </script>
经过上面的插槽功能就能够动态的切换输入框的标题,在一些需求中有多种内容输入方式,就不须要去定义多个组件来实现,只须要在父级组件来切换输入标题,子组件只负责输入操做,就能够实如今同一个组件上实现多个输入场景。
有了多种内容输入场景就必然须要多种输入提示,这种输入提示一定是须要与输入标题相配合,数据必然是一样与标题内容处于父级组件上,就须要多个插槽来实现,这时候就须要用到具名插槽:
1 <div id="app"> 2 <child> 3 <span slot="title">{{titleName}}:</span><!--插入标题插槽--> 4 <span slot="hint">{{hint}}</span> 5 </child> 6 </div> 7 <script> 8 var vm = new Vue({ 9 el:'#app', 10 components:{ 11 child:{ 12 template:` <div> 13 <slot name="title"></slot><!--定义标签具名插槽--> 14 <input type="text" /> 15 <slot name="hint"></slot><!--定义提示具名插槽--> 16 </div>` 17 } 18 }, 19 data:{ 20 titleName:'邮箱', 21 hint:"请输入正确的邮箱地址" 22 } 23 }); 24 </script>
经过具名插槽能够将父级插入的节点插入到指定的地方,固然这时候你会说能够直接在子组件上来实现这些数据切换,这是确定可行的,可是使用子组件实现就必然会涉及到父子组件传值。有可能你也会想到这样的功能也可使用一个父组件就能够实现,为何还要使用子组件呢?这也固然是能够的,可是这一样涉及到了另外一个问题,若是是复杂一点的需求呢?因此,没有绝对的设计优点,这要看具体需求,若是是在比较复杂的需求中就能够经过插槽的方式将输入操做与业务逻辑经过组件层级分离。
插槽除了上面的应用,在接下来的动态组件中也会有很大做用。
4.2动态组件:
所谓动态组件就是在父组件中定义一个子组件,能够经过数据来切换实现引入不一样的子组件,这时你必定会想这不就能够经过v-if和v-else来实现吗?注意,v-if和v-else来配合实现只能实现两个组件切换,并且须要在父组件中引入两个子组件,这与动态组件只须要引入一个子组件,而且能够切换多个子组件的功能来比较相差甚远。
语法:<component is=“绑定指定的子组件”></component>
示例:
1 <div id="app"> 2 <button @click="chanegCmp">切换组件</button> 3 <component :is="cmpType"></component> 4 </div> 5 <script> 6 const cmpOne = { 7 template:` <div> 8 <span>组件1:</span> 9 <input type="text" /> 10 </div>` 11 } 12 const cmpTwo = { 13 template:` <div> 14 <span>组件2:</span> 15 <input type="text" /> 16 </div>` 17 } 18 const cmpThree = { 19 template:` <div> 20 <span>组件3:</span> 21 <input type="text" /> 22 </div>` 23 } 24 var vm = new Vue({ 25 el:'#app', 26 data:{ 27 cmpList:["cmpOne","cmpTwo","cmpThree"], 28 cmpType:"cmpOne", 29 cmpPresent:0 30 }, 31 components:{ 32 cmpOne:cmpOne, 33 cmpTwo:cmpTwo, 34 cmpThree:cmpThree 35 }, 36 methods:{ 37 chanegCmp(){ 38 console.log(this.cmpPresent + ' - ' + this.cmpList.length) 39 if( (this.cmpPresent + 1) === this.cmpList.length){ 40 this.cmpPresent = 0; 41 this.cmpType = this.cmpList[this.cmpPresent]; 42 }else{ 43 this.cmpPresent ++; 44 this.cmpType = this.cmpList[this.cmpPresent]; 45 } 46 } 47 } 48 }); 49 </script>
示例实现效果:
虽然能够经过动态组件切换组件,可是上面的示例可能还并不能是咱们想要的,由于在切换组件时并不能保留组件的状态,好比咱们在第一次切换组件时,输入文本后第二次切换回到这个组件时,输入的文本并不能不被保留,也就是说从新切换回来的组件是一个全新的渲染组件,并不上次的原组件,
针对这种需求vue给我提供了一个标签<keep-alive>,使用这个标签包裹component标签就能够实现保留子组件的状态了,因此示例中的代码能够这样修改:
1 <keep-alive> 2 <component :is="cmpType"></component> 3 </keep-alive>
4.3做用域插槽
在前面的4.1中介绍了插槽,它能够在父级来定义相对灵活的子组件,在前面的示例中只是介绍了父级在子级指定为节点位置插入一些元素节点,这时我想应该又引起了咱们的另外一个思考,既然插槽能够在父级做用定义子级特定位置的节点,那可不能够实现父级做用域定义子级特定的数据模板呢?也就是子级不只能够提供一个占位,还能够提供数据引用来实如今父级组件中定义元素的节点和数据渲染。这个光是用文字来描述会有点绕,先来看一个小示例:
1 <div id="app"> 2 <child :userdata="user"><!--获取父组件的数据user,并定义新的引用名称userdata--> 3 <template slot-scope="list"><!--数据模板上经过slot-scope特性接收插槽传来的数据,并定义索引名list--> 4 <span>{{list.username}} -- {{list.userCom}}</span><!--经过模板接收到的数据索引取出数据并渲染到页面--> 5 </template> 6 </child> 7 </div> 8 <script> 9 const child = { 10 props:["userdata"],//接收父组件的数据userdata, 11 data(){ 12 return{ 13 comData:"child" 14 } 15 }, 16 template:` <div> 17 <!--经过插槽将接收到父组件的数据和子组件自身的数据传给模板--> 18 <!--传递数据的方式就是将数据以特性的方式传入slot标签--> 19 <slot 20 :username="userdata.name" 21 :userCom="comData"></slot> 22 <input type="text"/> 23 </div>` 24 } 25 var vm = new Vue({ 26 el:'#app', 27 data:{ 28 user:{ 29 name:"他乡踏雪", 30 } 31 }, 32 components:{ 33 child:child 34 } 35 }); 36 </script>
这里的一个关键就是经过<slot>插槽标签的特性键数据传出,而后经过模板特性slot-scope接收这些数据,再来提供一个示例来进一步了解做用域插槽:
1 <div id="app"> 2 <cmp-two :list="list"> 3 <template slot-scope="list"> 4 <li>{{list.item}} - {{list.index}}</li> 5 </template> 6 </cmp-two> 7 <cmp-two :list="list"> 8 <template slot-scope="list"> 9 <li>{{list.index}} - {{list.item}}</li> 10 </template> 11 </cmp-two> 12 </div> 13 <script> 14 const cmpTwo = { 15 props:['list'], 16 template:`<div> 17 组件2:<input tyle="text"> 18 <ul> 19 <slot v-for="(item,index) in list" 20 :item="item" 21 :index="index"> 22 </slot> 23 </ul> 24 </div>` 25 } 26 var vm = new Vue({ 27 el:'#app', 28 components:{ 29 cmpTwo 30 }, 31 data:{ 32 list:[1,2,3,4,5] 33 } 34 }); 35 </script>