2月6日-深入了解组件(6). 插槽
深入了解组件
1. 组件的注册
1.1 组件的注册方式
组件有两种注册方式: 1. 全局方式 2. 局部注册
全局注册:
到目前为止我们使用的都是全局注册的方式,也就是说它们在注册之后,可以再任何新建的Vue实例中使用。
Vue.component('组件名',{
//选项
})
局部注册:
new Vue({
el:'#app',
components:{
'组件1':{
// 选项
},
'组件2':{
//选项
}
}
})
使用局部注册,拒不注册的组件在其他Vue实例中不可以使用,并且在其子组件中也不可以使用。
<div id="app" >
<aa></aa>
<bb></bb>
</div>
<script>
var bb={
template:"<span>bbbbbb</span>"
}
new Vue({
el: "#app",
components: {
'aa':{
//子组件当中,无法使用父组件注册的bb,那需要重新注册
template:"<span>1234214<bb></bb></span>",
components: {
'bb':bb
}
},
'bb':bb
}
})
</script>
2. Prop
2.1. Prop的大小写
HTML当中对于属性名的大小写不敏感,所以浏览器会把所有的大写字符解释为小写字符,所以就意味着,我们在使用HTML作为模板代码时,prop名称为骆驼命名法的,要使用其等价的短横线分隔命名法。
iHaveAPen ==> i-have-a-pen
<div id="app" >
<aa i-have-a-pen="asdf"></aa>
</div>
<script>
new Vue({
el: "#app",
components: {
'aa':{
props:['iHaveAPen'],
template:'<span>1234{{iHaveAPen}}</span>'
}
}
})
</script>
如果我们是在template字符串中使用的时候,可以不适用短分割线命名法,因为template字符串是区分大小写的。
<div id="app" >
</div>
<script>
new Vue({
el: "#app",
components: {
'aa':{
props:['iHaveAPen'],
template:'<span>1234{{iHaveAPen}}</span>'
}
},
template:'<div> <aa iHaveAPen="asdf"></aa></div>'
})
</script>
2.2. Prop类型
目前为止,我们使用的prop定义都是:
props: ['title', 'likes', 'isPublished', 'commentIds', 'author']
这种方式,快速。我们现在要介绍的这种方式,写的会复杂些,但是,可以带来更好地类型检查和数据验证。
props:{
title:String,
likes:Number,
isPulished:Boolean,
commentIds:Array,
author:Object
}
可用的数据类型:
String 字符串
Number 数字
Boolean 布尔值
Array 数组
Object 对象
Date 日期
Function 函数
2.2.传递静态或者动态数据
<div id="app" >
字符串动态: <aa :title="artice.title" ></aa>
<hr>
字符串静态: <aa title="yyy"></aa>
<hr>
数字动态:<aa :likes="artice.likes"></aa>
<hr>
布尔值静态,写了就是true:<aa is-pulished></aa>
<hr>
布尔值动态:<aa :is-pulished='artice.isPulished'></aa>
<hr>
数组动态:<aa :comment-ids="artice.commentIds"></aa>
<hr>
对象动态:<aa :author="artice.author"></aa>
<hr>
一次性传入对象所有属性:<aa v-bind="artice"></aa>
</div>
<script>
new Vue({
el: "#app",
data:{
artice:{
title:"xxx",
likes:3,
isPulished:true,
commentIds:[1,2],
author:{
name:"小明"
}
}
},
components: {
'aa':{
props:{
title:String,
likes:Number,
isPulished:Boolean,
commentIds:Array,
author:Object
},
template:`<span>
title:{{title}},
likes:{{likes}},
isPulished:{{isPulished}},
commentIds:{{commentIds}},
author:{{author}}
</span>`
}
}
})
2.3.单向数据传递。
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态
每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
注意在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
我们只要看传入的数据,是在传入值类型还是引用类型就可以判断了。
我们在实际项目中,会遇到需要高边prop值的情况:
- 这个prop用来传递一个初始值,这个初始值随后会被子组件内容改变。这种情况下,最好的处理方式,就是建立一个组件内部的data属性,将这个prop值作为data属性的初始值。
<div id="app" >
<div>
{{artice.title}}
<input type="text" v-model="artice.title">
</div>
<hr>
<aa :title="artice.title"></aa>
</div>
<script>
new Vue({
el: "#app",
data:{
artice:{
title:"xxx"
}
},
components: {
'aa':{
props:{
title:String
},
data(){
return {
//建立一个t属性,初值为title的值
t:this.title
}
},
template:`<div>
title:{{title}},
t:{{t}},
<input v-model="t">
</div>`
}
}
})
</script>
- 需要这个prop值进行一些转换,这种情况我们用computed创造经过计算之后的值
<div id="app" >
<div>
{{artice.title}}
<input type="text" v-model="artice.title">
</div>
<hr>
<aa :title="artice.title"></aa>
</div>
<script>
new Vue({
el: "#app",
data:{
artice:{
title:"xxx"
}
},
components: {
'aa':{
props:{
title:String
},
template:`<div>
title:{{title}},
t:{{t}}
</div>`,
computed: {
t:function(){
return this.title.toUpperCase()
}
}
}
}
})
</script>
2.4.prop验证和类型检验
props:{
title:String,
likes:[String,Number],
isPulished:Boolean,
commentIds:Array,
author:Object
}
可以扩展成下面的形式
props:{
title:{
type:String,
required:true,//必须传递
default:'abc',
//自定义验证规则,返回false时会在控制台报错
validator:function(value){
return value.length>5;
}
},
likes:{
type:[String,Number]
},
isPulished:{
type:Boolean,
default:true //没有传递数据则默认值为true
},
commentIds:{
type:Array,
//对于Array和Object类型,需要提供数据构造函数来返回数据
default:function(){
return [1,2,4]
}
},
author:{
type:Object
}
}
2.5. 非Prop的属性
Vue会把父组件加在子组件上的属性,自动附加到子组件的根元素上。
<div id="app">
<div>
{{artice.title}}
<input type="text" v-model="artice.title">
</div>
<hr>
<aa class="abc" style="color:red" a="1" b="2" :likes="'123'"></aa>
</div>
<script>
new Vue({
el: "#app",
data: {
artice: {
title: "xxx"
}
},
components: {
'aa': {
props: {
title:{
type:String,
default:'abc1234',
//自定义验证规则
validator:function(value){
return value.length>5;
},
},
likes: {
type: [String, Number]
},
isPulished:{
type:Boolean,
default:true
},
commentIds:{
type:Array,
default:function(){
return [1,2,4]
}
},
},
template: `<div>
{{title}} {{likes}} {{isPulished}} {{commentIds}}
</div>`,
computed: {
t: function () {
return this.title.toUpperCase()
}
}
}
}
})
</script>
3. 自定义事件
3.1. 自定义事件命名
事件名不像prop名称、组件名称,事件名不存在任何大小写的自动转换,而是触发的事件名需要和监听的名称完全匹配。
举个例子,比如我们要触发talkSomething事件
this.$emit('talkSomething')
如果我们按之前的思维,将驼峰命名法改为短横线命名法去监听。
<!--无效-->
<aa v-on:talk-something="doSomething"></aa>
<aa v-on:talkSomething="doSomething"></aa>
talk-something => talk-something
talkSomething => talksomething
所以我们发现,无论是talk-something还是talkSomething,由于html属性中不区分大小写,会全部理解成小写字符,所以无法跟有大写字符的talkSomething事件相匹配。
vue当中的自定义事件,名称中不要出现大写字符,如果需要做单词分隔的,使用短横线命名法。
<div id="app">
{{a}}
<aa @talk-something="doSomething"></aa>
</div>
<script>
new Vue({
el: "#app",
components: {
'aa': {
template:`<button @click="$emit('talk-something')">xxxxx</button>`
}
},
methods: {
doSomething(){
this.a="asdfjsoidfj";
}
},
data:{
a:''
}
})
</script>
3.2. 将原生事件绑定到组件根节点
通过.native,可以实现对组件根节点监听浏览器原生事件。
注意,使用这种方式只能监听浏览器原生事件,
<div id="app">
{{a}}
<aa @input.native="doSomething" ></aa>
</div>
<script>
new Vue({
el: "#app",
components: {
'aa': {
template:`<div> {{x}} <input > </div>`
}
},
methods: {
doSomething(){
this.a="监视到input事件";
}
},
data:{
a:''
}
})
</script>
4.动态组件
有的时候,我们需要在不同的组件之间进行切换。
<component is="bb"></component>
通过vue自带的component组件,我们可以动态加载其他组件。
默认情况下,组件失活之后,会被销毁,如果我们让失活的组件被缓存下来,那只需要在外侧包一层
<keep-alive>
<component :is="cname"></component>
</keep-alive>
<div id="app">
<input type="text" v-model="cname">
<keep-alive>
<component :is="cname"></component>
</keep-alive>
</div>
<script>
new Vue({
el: "#app",
components: {
'aa': {
template:`<div>A <input > </div>`
},
'bb':{
template:`<div>B <input > </div>`
}
},
data:{
cname:'aa'
}
})
</script>
练习10
- 点击左侧导航菜单,如果没有改标签页,就新增一个。并且切换到显示该标签页内容。
- 点击标签上的xx,可以将标签页关闭
5. 插槽
5.1. 后备内容
后备内容很好理解,就是插槽内可以放置默认内容,如果使用插槽的时候有传入插槽的内容的话就用传入的,没有的话就会使用默认内容。
<div id="app">
<aa>
111
</aa>
</div>
<script>
new Vue({
el: "#app",
components: {
'aa': {
template:`<div>
<p>1</p>
<slot name="default">
<button>asdfasdf</button>
<button>asdfasdf</button>
</slot>
<p>2</p>
</div>`
},
}
})
</script>
5.2. 作用域插槽
有时候我们需要在使用插槽的时候,从父组件访问子组件中的数据。这时候就会用到作用域插槽。
作用域插槽就是在定义插槽的时候,在slot标签上通过v-bind指令去绑定prop,可以绑定多个。
然后父组件去使用这个插槽的时候,通过v-slot指令等号后面的部分,去定义一个变量,接收子组件插槽提供的数据。
<div id="app">
<aa>
<template v-slot:thead>
<tr><th>产品名</th><th>单价</th><th>件数</th><th>小计</th></tr>
</template>
<template v-slot:default="row">
<tr>
<td>{{row.x.name}}</td>
<td>{{row.x.price}}</td>
<td>{{row.x.count}}</td>
<td>{{row.x.count*row.x.price}}</td>
</tr>
</template>
</aa>
</div>
<script>
new Vue({
el: "#app",
components: {
'aa': {
template:`<div>
<table>
<slot name="thead" ></slot>
<slot :x="item" :y="1234" v-for="item in items" ></slot>
</table>
</div>`,
data(){
return {
items: [{name:'乒乓球',price:20,count:10},{name:'足球',price:100,count:1}]
}
}
}
}
})
</script>