组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用is特性进行了扩展的原生 HTML 元素。组件注册的时候需要为该组件指定各种参数。
因为组件是可复用的 Vue 实例,所以它们与 new Vue
接收相同的选项,例如 data
、computed
、watch
、methods
以及生命周期钩子等。仅有的例外是像 el
这样根实例特有的选项。
<body>
//模板
<div id="app"></div>
<script>
new Vue({
el:'#app',
data:{},
methods:{}
})
</script>
</body>
<body>
//挂载好的模板
<div id="app"></div>
<script>
//全局声明
Vue.component("my-header",{
data(){
return{
num:10
}
},
methods:{}
});
//实例对象 根组件
new Vue({
el:'#app',
data:{},
methods:{}
})
</script>
</body>
以上两段代码不同之处就很明显了,第一段代码在根组件中,所以存在el,在组件中el只有一个,另一个不同在于在根组件中的data是对象,在我们声明的组件中,data必须为一个函数,可以使用function或者()调用,并且设置返回值。
全局注册
通过template来实现组件的复用,template代表该组建的模板,只能有一个直接孩子节点。
要想进行组件使用得先进行组件注册,全局注册可以使用Vue.component(tagName, options) 注册一个全局组件, 注册之后可以用在任何新创建的 Vue 根实例的模板中。
<body>
//利用组件可以在模板中无限复用
<div id="app">
<my-header></my-header>
<my-header></my-header>
<my-header></my-header>
</div>
<script>
//全局声明
Vue.component("my-header",{
data(){//函数
return{//函数中必须有return
num:10
}
},
methods:{
handler(){
this.num++;//返回值num++
}
},
//template代表该组建的模板 只能有一个直接孩子节点
//将想要复用的元素封装起来
template:`
<div>
<p>11111-{{num}}</p>
<p>22222</p>
<button @click='handler'>点击</button>
//定义了一个点击事件的方法handler,每个点击事件是独立的
</div>
`
});
//实例对象
new Vue({
el:'#app',
data:{//对象
},
methods:{}
})
</script>

局部注册
注册在vue实例声明中,使用components:{},和data,methods,computed(计算属性)同级
<body>
<div id="app">
<my-header></my-header>
<my-footer></my-footer>
</div>
<script>
new Vue({
el:'#app',
data:{},
methods:{},
components:{
//多个组件声明
//属性名即为组件名称,属性值为参数
'my-footer':{
data(){
return{}
},
template:`
<div>hello-footer</div>
`
},
'my-header':{
data(){
return{
num:0
}
},
methods:{
handler(){
this.num++;
}
},
template:`
<div>
<p>11111-{{num}}</p>
<p>22222</p>
<button @click='handler'>点击</button>
</div>
`
}
}
})
</script>
</body>
但是兄弟级组件之间不能嵌套使用,可以使用全局嵌套。
<body>
<div id="app">
<my-header></my-header>
<!-- <my-footer></my-footer> -->
</div>
<script>
Vue.component('my-footer',{
data(){
return{}
},
template:`
<div>hello-footer</div>
`
});
new Vue({
el:'#app',
data:{},
methods:{},
components:{
//多个组件声明
//属性名即为组件名称,属性值为参数
'my-header':{
data(){
return{
num:0
}
},
methods:{
handler(){
this.num++;
}
},
template:`
<div>
<p>11111-{{num}}</p>
<p>22222</p>
<my-footer></my-footer>
<button @click='handler'>点击</button>
</div>
`
}
}
})
</script>
</body>

那么组件之间如何传值呢?
父-->子:props[]
在父组件的template中定义所需要的自定义属性,属性值为传输的内容
<my-footer msg='hello-父组件给子组件的内容'></my-footer>
然后在子组件中添加一个与data,methods同级的属性props[],props[]代表的是子组件希望从父组件中接收的属性,多个属性用逗号隔开
props:["msg"]
子--->父:$emit();
在子组件中使用事件绑定触发父级的自定义事件,$emit("name",data,data1);然后在父组件中绑定自定义事件,在自定义事件的处理程序中,参数为传递的变量
<body>
<div id="app">
<my-header></my-header>
<!-- <my-footer></my-footer> -->
</div>
<script>
Vue.component("my-footer",{
//子组件
data(){
return {
substr:"my-footer"
}
},
methods:{
send(){
this.$emit("accept",this.substr,"send1");//第一个参数为触发父亲哪一个事件 触发方法时要不要携带参数
}
},
props:["msg","str",'num'],//子组件希望从父组件中接受的属性
template:`
<div>
<p @click='send'>click-send</p>
hello-footer-{{msg}}
<p>{{str}}</p>
<p>{{num}}</p>
</div>
`
});
var header={//父组件
data(){
return {
num:0,
substr:''
}
},
methods:{
handler(){
this.num++;
},
accept(data,data1){
this.substr=data;
console.log(data,data1);
}
},
template:`
<div>
<p>{{substr}}</p>
<p>one row--1111-{{num}}</p>
<p>two row--2222</p>
<my-footer v-on:accept='accept' msg='hello-父组件给子组件的内容' str='my-header' :num='num'></my-footer>
<button @click='handler'>click</button>
</div>`
}
new Vue({
el:'#app',
data:{},
methods:{},
components:{
//多个组件声明
//属性名即为组件名称,属性值为参数
'my-header':header
}
})
</script>
</body>
下面是利用父子组件制作的一个小案例:有四个独立的计数器,可以进行加减操作,设置一个总计的计数器,计算四个计数器的总值。
代码效果如下:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.min.js"></script>
</head>
<body>
<div id="app">
<div>
总计: {{total}}
</div>
<test @rr='rr' @aa='aa'></test>
<test @rr='rr' @aa='aa'></test>
<test @rr='rr' @aa='aa'></test>
<test @rr='rr' @aa='aa'></test>
</div>
<script>
let test={ //声明一个子组件
template:`
<div>
<div>数量:{{count}}</div>
<div>
<button @click='r'>减少</button>
<button @click='a'>增加</button>
</div>
</div>
`,
data(){
return {
count:0
}
},
methods:{
r(){
this.count--
this.$emit('rr')
},
a(){
this.count++
this.$emit('aa')
}
}
}
new Vue({//父组件
el:'#app',
components:{
test
},
data:{
total:0
},
methods:{
rr(){
this.total--
},
aa(){
this.total++
}
}
})
</script>
</body>
</html>
props验证
如果传递的参数不符合定义的类型,vue会发出警告
<body>
<div id="app">
<parent></parent>
</div>
<script>
Vue.component("child",{
data(){
return {}
},
props:{
str:String,
str1:[String,Number],
str2:{
type:String,
required:true
},
str3:{
type:Number,
default:99
},
str4:{
type:Object,
default:function(){
return {name:"tom"}
}
},
str5:{
validator(value){
return value.indexOf("o") != -1
}
}
},
template:`
<h3>子组件:{{str5}}</h3>
`
});
new Vue({
el:'#app',
data:{},
methods:{},
components:{
"parent":{
data(){
return {
str:"10",
str1:10,
str4:{
name:"lisi"
},
str5:'hell'
}
},
template:`
<div>
<h1>父组件:</h1>
<child :str='str' :str1='str1' :str2='str' :str3='str1' :str5='str5'></child>
</div>
`
}
}
});
</script>
</body>
插槽
插槽允许我们在调用子组件的时候为子组件传递模板。
1)普通插槽:通过<slot></slot>标签声明一个区域,用来存放形参
2)具名插槽:在给slot起名字时通过name属性,在使用时,通过<template v-slot:name></template>指定绑定的插槽
3)作用域插槽:在slot标签中通过v-bind绑定插槽可以使用的属性,然后在template标签中使用v-slot:default=name,default是默认属性名,如果设置了自定义属性名需要更改。name:template标签中就是当前可以使用的作用域。
<body>
<div id="app">
<my-div>
<!-- 利用template标签 不会增加盒子 但是可以操作元素 -->
<template v-slot:default='scope'>
<!-- v-slot:default 是默认属性名 -->
{{scope.user.name}}
{{scope.list}}
</template>
<template v-slot:header>
你好
</template>
slit2-----
</template>
</my-div>
</div>
<script>
new Vue({
el:'#app',
data:{},
methods:{},
components:{
'my-div':{
data(){
return {
user:{
name:'lisi',
age:20
},
list:[1,2,3,4,5]
}
},
template:`
<div>
<p>hello</p>
<p>world</p>
/*通过插槽 设置形参*/
<p><slot :user='user' :list='list'>slot</slot></p>
<p><slot name='header'>slot</slot></p>
<p><slot name='footer'>slot</slot></p>
</div>
`
}
}
});
</script>
</body>