Vue中利用组件化的思想把页面拆成一个个小的功能块(组件),每个功能块完成自己这部分独立的功能。
一、组件的基本使用
注册组件的基本步骤
- 创建组件构造器 – 调用Vue.extend()方法
- 注册组件 – 调用Vue.component()方法
- 使用组件 – 在Vue实例的作用范围内使用
代码示例:
<div id="app">
<!-- 3.使用组件 -->
<mycpn></mycpn>
<mycpn></mycpn>
<mycpn></mycpn>
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是模板1</h2>
<p>hello,world!</p>
<p>1234556</p>
</div>`
// ES6语法中使用``,相比引号来说可以实现不用+号换行
})
// 2.注册组件,传入组件名与组件对象
Vue.component('mycpn' , cpnC)
// 注意:不能把创建和注册组件放到new Vue的后面
const app = new Vue({
el:'#app',
data:{
message:'hello'
}
})
</script>
步骤解析:
- Vue.extend():创建一个组件构造器,传入template代表组件模板,比如要显示的html代码。此方法在Vue2.x中直接使用语法糖。
- Vue.component():注册组件,并给组件一个标签名,所以传入1.组件标签名,2.组件构造器对象。
- 使用组件必须在挂载到Vue实例下。
二、全局组件与局部组件
通过以下代码示例:
<div id="app">
<cpn></cpn>
</div>
<script>
const cpn1 = Vue.extend({
template:`
<h2>我是模板1</h2>
`
})
// 全局注册(可在多个Vue实例中使用)
Vue.component('cpn',cpn1)
const app = new Vue({
el:'#app',
data:{
message:'hello'
},
// 局部注册(只能在该实例下使用)
components:{
// 组件标签名:组件对象
cpn:cpn1
}
})
</script>
在实际开发中,一般只创建一个实例,局部组件使用的更多。
三、父组件和子组件
组件与组件之间存在层级关系,在一个组件中可以对另一个组件进行注册,被注册的组件为子组件,注册的组件为父组件。
代码示例:
<div id="app">
<cpn2></cpn2>
</div>
<script>
// 1.创建组件1-子组件
const cpn1 = Vue.extend({
template:`
<div>
<h2>模板1</h2>
</div>
`
})
// 2.创建组件2-父组件
const cpn2 = Vue.extend({
template:`
<div>
<h2>模板2</h2>
<cpn1></cpn1>
</div>
`,
// 在父组件中注册子组件
components:{
cpn1 : cpn1
}
})
// root组件
const app = new Vue({
el:'#app',
data:{
message:'hello'
},
components:{
cpn2: cpn2
}
})
</script>
四、注册组件语法糖
在开发中常常省略extend方法,直接使用component方法进行组件的注册。
代码示例:
Vue.component('cpn1',{
template:`
<div>
<h2>模板1</h2>
</div>
`
})
父子组件写法:
Vue.component('cpn1',{
template:`
<div>
<h2>模板1</h2>
<cpn2></cpn2>
</div>
`,
components:{
'cpn2':{
template:`
<div>
<h2>模板1</h2>
</div>
`
}
}
})
五、组件模板分离的写法
为了使代码更加简洁美观,在开发中常常将组件模板template的内容分离出去,有以下两种方法:
- script标签
<script type="text/x-template" id='cpn'>
<div>
<h2>我是模板1</h2>
</div>
</script>
- template标签
<template id="cpn">
<div>
<h2>我是模板2</h2>
</div>
</template>
分离出去后,在注册组件时通过id调用即可:
Vue.component('cpn',{
template:'#cpn'
})
六、组件中的data属性
组件不能直接调用Vue实例中的data属性,但是在component方法中也含有自己的data属性用于存放自己的属性(也有methods等属性),这个data必须是一个函数,并且返回一个保存着数据的对象。
<div id="app">
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>{{message}}</h2>
</div>
</template>
<script>
Vue.component('cpn',{
template:'#cpn',
data(){
return {
message:'我是组件的data'
}
}
})
组件中的data为函数的原因是能够使用组件时,data函数返回属于实例自己的对象,从而不会相互影响,下面的代码示例了一个用组件封装的计数器:
<div id="app">
<cpn></cpn>
<cpn></cpn>
<cpn></cpn>
</div>
<template id="cpn">
<div>
<h2>当前计数:{{count}}</h2>
<button @click='sub' :disabled = 'this.count<=0'>-</button>
<button @click='add'>+</button>
</div>
</template>
<script>
// 1.注册组件
Vue.component('cpn',{
template:'#cpn',
data(){
return {
count:0
}
},
methods: {
sub(){
this.count--
},
add(){
this.count++
}
},
})
const app = new Vue({
el:'#app',
data:{
}
})
</script>
七、组件之间的通信
在开发中,常常会遇到页面大组件从服务器请求了很多数据,然后由下面的子组件进行展示的情况,这个时候就是直接让父组件将数据传递给子组件。
子组件是不能引用父组件或者Vue实例(根组件)的数据的,而是通过以下两种方式:
- 通过props向子组件传递数据
- 通过事件向父组件发送消息
7.1 props基本用法(父传子)
props支持使用数组与对象类型,下面例子演示了数组写法:
<div id="app">
<!-- 3.将根组件的值动态绑定给子组件的属性 -->
<cpn :childmsg='message' :childmkt='milktea'></cpn>
</div>
<template id="cpn">
<div>
<h2>{{childmsg}}</h2>
<ul>
<li v-for='itm in childmkt'>{{itm}}</li>
</ul>
</div>
</template>
<script>
// 2.注册子组件,利用props定义一个数组属性
const cpn = {
template:'#cpn',
data() {
return {
}
},
props:['childmsg','childmkt']
}
// 1.在根组件中绑定子组件,并传入message和milktea的值
const app = new Vue({
el:'#app',
data:{
message:'hello',
milktea:['一点点','烧仙草','茶百道','丸摩堂']
},
components:{
cpn
}
})
</script>
7.2 props数据验证
当需要对props数据进行验证时,需要使用对象写法。
验证支持:String、Number、Boolean、Array、Object、Date、Function、Symbol,也支持自定义构造函数。
props:{
// 1.类型限制
childmsg: String,
// 2.提供一些默认值,以及定义是否必须传值
childstr:{
type: String,
default: '默认值',
required: true
},
// 3.类型是对象或者数组时,默认值必须是一个函数
childmkt:{
type: Array,
default(){
return []
}
}
}
注:在props中定义属性名时,尽量避免使用驼峰命名法,如果使用了驼峰命名法,在把父组件的值绑定给子组件时,子组件名需要在大写字母前加“-”。
示例,当子组件props中含有一个“cMessage”属性,需要进行如下绑定方法:
<div id="app">
<cpn :c-message = 'Message'></cpn>
</div>
7.3 自定义事件(子传父)
子组件传给父组件,常常通过以下步骤:
- 在子组件中通过$emit 发送自定义事件;
- 在父组件DOM中使用v-on监听子组件的自定义事件;
- 在父组件实例中通过methods处理子组件的自定义事件。
代码示例:
<div id="app">
<!-- 2.在父组件DOM中,通过v-on监听子组件的自定义事件 -->
<cpn @itmclick='cpnClick'></cpn>
</div>
<template id="cpn">
<div>
<button v-for = 'itm in categories' @click = 'btnclk(itm)'
>{{itm.name}}</button>
</div>
</template>
<script>
const cpn = {
template:'#cpn',
data() {
return {
categories:[
{id:'a',name:'热门推荐'},
{id:'b',name:'手机数码'},
{id:'c',name:'家用家电'},
{id:'d',name:'电脑办公'}
]
}
},
methods: {
btnclk(itm){
// 1.子组件发送事件,自定义事件
this.$emit('itmclick',itm)
}
},
}
const app = new Vue({
el:'#app',
data:{
},
components:{
cpn
},
methods:{
// 3.在父组件中进行处理
cpnClick(itm){
console.log(itm);
}
}
})
</script>
7.4 父子组件通信示例
下面的代码演示了子组件获取父组件data中的参数值,并且通过修改input输入框里的value,使用$emit方法将inpur 中的event事件value传给父组件,达到修改父组件中data的值的目的。
<div id="app">
<cpn :cnum1='num1' :cnum2='num2'
@cnum1change='num1change'></cpn>
</div>
<template id="cpn">
<div>
<h2>data:{{dnum1}}</h2>
<h2>props:{{cnum1}}</h2>
<input type="text" :value='dnum1' @input = 'cnum1input'>
</div>
</template>
<script>
const app = new Vue({
el:'#app',
data:{
num1:1,
num2:0
},
methods: {
num1change(value){
this.num1 = parseFloat(value)
}
},
components:{
cpn:{
template:'#cpn',
props:{
cnum1:Number,
cnum2:Number
},
data() {
return {
dnum1:this.cnum1,
dnum2:this.cnum2
}
},
methods: {
cnum1input(event){
this.dnum1 = event.target.value;
this.$emit('cnum1change', this.dnum1)
}
},
}
}
})
</script>
运行效果:
7.5 父子组件的访问方式
父子组件也可以相互之间访问,
- 父组件访问子组件:
$children
或$refs
- 子组件访问父组件:
$parent
7.5.1 父访问子:$children
或$refs
在根组件中添加了一个子组件cpn后,可以在根组件中通过$children
的方法直接获取到子组件,DOM中存在多个子组件时,可加上索引,也可以直接获取到子组件中的属性值。父组件的methods如下:
methods: {
btnclk(){
console.log(this.$children[0]);
console.log(this.$children[0].name);
}
触发点击函数时,会有以下效果:
在开发中,常常使用$refs
方法用于父组件访问子组件,只需要在DOM中的组件标签里添加上ref属性:
<cpn ref='cpnref'></cpn>
再在父组件的method中利用以下方法获取ref值,就可以产生同样的效果:
btnclk(){
console.log(this.$refs.cpnref)
console.log(this.$refs.cpnref.name)
}
7.5.2 子访问父:$parent
和 $root
同样的,在子组件的methods中,可通过$paren
t访问父组件,也可通过$root
直接访问根组件。
子组件的methods如下:
methods: {
cbtnclk(){
console.log(this.$parent.message)
console.log(this.$root)
}
}
效果如下: