组件是Vue.js最核心的功能,也是整个框架设计最精彩的地方,当然也是最难掌握的。
组件与复用
组件用法
回顾一下我们创建Vue实例的方法
var app = new Vue({
el: '#app'
})
组件与之类似,需要注册后才能使用。注册有全局注册和局部注册两种方式。全局注册后,任何Vue实例都可以使用。全局注册示例代码如下:
<div id="app">
<my-component></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component', {
template: '<div>我是组件{{msg}}</div>',
data() {
return {
msg: '我是组件的data'
}
}
})
var app = new Vue({
el: '#app'
})
</script>
局部组件
<div id="app">
<my-component></my-component>
<login></login>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
components: {
// 如果没有-,可以不带引号
login: {
template: '<div>login</div>'
},
'my-component': {
template: '<div>局部组件</div>'
}
}
})
</script>
Vue组件的模板在某些情况下会受到HTML的限制,比如
内规定只允许是,可以使用特殊的is属性来挂载组件 <div id="app">
<table>
<tbody is="my-component">
</tbody>
</table>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
template: '<div>这是组件的内容</div>'
})
var app = new Vue({
el: '#app',
})
</script>
使用props传递数据
基本用法
在组件中,使用选项props来声明需要从父级接收的数据,props的值可以是两种,一种是字符串数组,一种是对象,先介绍数组的用法,比如我们构造一个数组,接受一个来自父级的数据message,并把它在数组模板中渲染
<div id="app">
<my-component message="来自父组件的数据">
</my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component', {
props: ['message'],
template: '<div>{{message}}</div>'
})
var app = new Vue({
el: '#app',
})
</script>
props中声明的数据与组件data函数return的数据主要区别就是props来自父级,而data中的是组件自己的数据,作用域是组件本身,这两种数据都可以在模板template及计算属性computed和方法methods中使用。上例的数据message就是通过props从父级传递过来的,在组件的自定义标签上直接写该props的名称,如果要传递多个数据,在props数组中添加项即可
由于HTML特性不区分大小写,当使用DOM模板时,驼峰命名的props改为短横线分隔
<div id="app">
<my-component message-message="来自父组件的数据">
</my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component', {
props: ['messageMessage'],
template: '<div>{{messageMessage}}</div>',
data() {
return{
msg: '组件自己的数据'
}
}
})
var app = new Vue({
el: '#app'
})
</script>
很多时候,传递的数据不是写死的,而是来自父级的动态数据,可以使用v-bind动态绑定props的值,当父组件数据变化时,会传递给子组件
<div id="app">
<input type="text" v-model="parentMessage">
<my-component :message="parentMessage">
</my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component', {
props: ['message'],
template: '<div>{{message}}</div>',
data() {
return{
msg: '组件自己的数据'
}
}
})
var app = new Vue({
el: '#app',
data:{
parentMessage:''
}
})
</script>
这里用 v-model 绑定了父级的数据 parentMessage ,当通过输入框任意输入时,子组件接收到
props"message"也会实时响应,并更新组件模板。
组件通信
父组件向子组件通信,通过props传递数据,但Vue组件通信的场景不止有这一种,归纳起来,组件之间通信可以用下图表示
自定义事件
当子组件需要向父组件传递数据时,就要用到自定义事件,我们在介绍指令v-on时有提到,v-on除了监听DOM事件外,还可以用于组件之间的自定义事件
如果你了解过Javascript的设计模式————观察者模式,一定知道dispatchEvent和addEventListener这两种方法。Vue组件也有与之类似的一套模式,子组件用 e m i t ( ) 来 触 发 事 件 , 父 组 件 用 emit()来触发事件,父组件用 emit()来触发事件,父组件用on()来监听子组件的事件。
父组件也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件,示例
<div id="app">
<p>总数:{{total}}</p>
<my-component @increase="handleGetTotal" @reduce="handleGetTotal">
</my-component>
</div>
<template id="com">
<div>
<button @click="handleIncrease">
+1
</button>
<button @click="handleReduce">
-1
</button>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component', {
template: '#com',
data() {
return {
counter: 0
}
},
methods: {
handleIncrease() {
this.counter++;
this.$emit('increase', this.counter);
},
handleReduce() {
this.counter--;
this.$emit('reduce', this.counter);
}
}
})
var app = new Vue({
el: '#app',
data: {
total: 0
},
methods: {
// 父组件内有方法,子组件内部通过触发自己的事件,然后通过this.$emit调用父组件的方法,同时传递值
handleGetTotal(total) {
this.total = total;
}
}
})
</script>
上边示例,子组件有两个按钮,分别实现加1和减1的效果,在改变组件的data
"counter"后,通过
e
m
i
t
(
)
再
把
它
传
递
给
父
组
件
,
父
组
件
用
v
−
o
n
:
i
n
c
r
e
a
s
e
和
v
−
o
n
:
r
e
d
u
c
e
。
emit()再把它传递给父组件,父组件用v-on:increase和v-on:reduce。
emit()再把它传递给父组件,父组件用v−on:increase和v−on:reduce。emit()方法的第一个参数是自定义事件的名称,例如示例的increase和reduce后面的参数都是要传递的数据,可以不填或者填写多个
使用v-model
Vue2.x可以在自定义组件
<div id="app">
<p>总数:{{total}}</p>
<my-component v-model="total"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component', {
template: '<button @click="handleClick">+1</button>',
data() {
return {
counter: 0
}
},
methods: {
handleClick() {
this.counter++;
this.$emit('input', this.counter)
}
}
})
var app = new Vue({
el: '#app',
data: {
total: 0
}
})
</script>
和上边一样
<div id="app">
<p>总数:{{total}}</p>
<my-component @input="handleGetTotal"></my-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component', {
template: '<button @click="handleClick">+1</button>',
data() {
return {
counter: 0
}
},
methods: {
handleClick() {
this.counter++;
this.$emit('input', this.counter)
}
}
})
var app = new Vue({
el: '#app',
data: {
total: 0
},
methods: {
handleGetTotal(total) {
this.total = total;
}
}
})
</script>
先看一个比较常规的网站布局,这个网站由一级导航,二级导航,左侧列表,正文以及底部版权信息5个模块组成,如果要将他们都组件化,这个结构可能会是:
<app>
<menu-main></menu-main>
<menu-sub></menu-sub>
<div class="container">
<menu-left></menu-left>
<container></container>
</div>
<app-footer></app-footer>
</app>
当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到slot,这个过程叫做内容分发。以为例,它有两个特点
- 组件不知道它的挂载点会有什么内容,挂载点的内容是由的父组件决定的
- 组件很可能有它自己的模板
props传递数据、events触发事件和slot内容分发就构成了Vue组件的3个API来源,再复杂的组件也是由这3部分构成的
作用域
正式介绍slot前,需要先知道一个概念:编译的作用域。比如父组件中有如下模板:
<child-component>
{{message}}
</child-component>
这里的message就是一个slot,但是它绑定的是父组件的数据,而不是组件的数据。
父组件模板的内容是在父组件作用域内编译,子组件模板的内容实在子组件作用域内编译,例
<div id="app">
<!-- 这里的状态showChild绑定的是父组件的数据, -->
<child-component v-show="showChild">
</child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child-component', {
template: '<div>子组件</div>'
})
var app = new Vue({
el: '#app',
data: {
showChild: true
}
})
</script>
<!-- 这里的状态showChild绑定的是父组件的数据,如果想在子组件上绑定,那应该是下边 -->
<div id="app">
<child-component>
</child-component>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child-component', {
template: '<div v-show="showChild">子组件</div>',
data() {
return {
showChild: true
}
}
})
var app = new Vue({
el: '#app'
})
</script>
因此, slot 分发的内容,作用域是在父组件上的。