关键词:组件、slot
组件是Vue.js最核心的功能,组件应注重复用性
组件用法
创建组件与创建Vue实例的方法类似,都需要先注册后才可以使用。注册分为全局注册和局部注册两种方式。全局注册后,任何Vue实例都可以使用:
<script>
Vue.component('my-component',{
//选项
})
</script>
my-component就是注册的组件的自定义标签名称
要在父实例中使用这个组件,必须在实例创建前注册,之后便可用
<my-component></my-component>来使用组件
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component('my-component',{
//选项
});
new Vue({
el:'#app'
})
</script>
在上面的代码实例中,显示出来的内容是一片空白,因为组件中没用任何内容,我们需要在组件中添加template来显示组件内容:
Vue.component('my-component',{
template:'<div>这里是组件的内容</div>'
});
template 的 DOM 结构必须被一个元素包含,如果在这里去掉<div>,只保留“这里是组件的内容”,这样是无法渲染的。
在Vue实例中,可以用components选项进行局部注册组件,该组件只有在实例作用域下有效。组件中也可以使用components选项来注册组件,使组件嵌套:
<div id="app">
<my-component></my-component>
</div>
<script>
var Child = {
template:'<div>局部注册组件的内容</div>'
}
var vm = new Vue({
el:'#app',
components:{
'my-component':Child
}
})
</script>
Vue组件的模板在某些情况下会受到HTML的限制,比如<table>内规定只允许是<tr>、<td>、<th>等表格元素,所以无法直接在table标签内使用组件。
这种情况,可以使用特殊的 is 属性来挂载组件:
<div id="app">
<table>
<tbody is="my-component"></tbody>
</table>
</div>
<script>
Vue.component('my-component',{
template:'<div>这里是组件的内容</div>'
})
var vm = new Vue({
el:'#app'
})
tbody在渲染时,会被替换为组件的内容。类似的受限制元素还有<ul> <ol> <select>
除了template选项外,组件中还可以像Vue实例那样使用其他的选项,比如data、computed、methods 等。但是在使用data时必须是函数,然后将数据return 出去:
<div id="app">
<my-component></my-component>
</div>
<script>
Vue.component('my-component',{
template:'<div>{{message}}</div>',
data(){
return {
message:'组件内容'
}
}
})
var vm = new Vue({
el:'#app'
})
</script>
JavaScript对象是引用关系,如果return出的对象引用了外部的一个对象,那这个对象就是共享的,任何一方修改都会同步。
如果你希望每个组件独立,不被其他同类型的组件影响,那就让组件返回一个新的data对象:
Vue.component('my-component',{
template:'<button @click="counter++">{{counter}}</button>',
data(){
return {
counter:0
}
}
})
这样,每个<my-component></my-component>就是独立的,互相不会影响显示的counter数
使用props传递数据
基本用法
组件的作用不仅仅是把模板的内容进行复用,更重要的是组件间需要进行通信。
在组件中,使用选项props来声明需要从父级接收的数据,props的值可以是两种,一种是字符串数组,一种是对象。
例如构造一个数组,接收一个来自父级的数据message,并将它在组件模板中渲染:
<div id="app">
<my-component message="来自父级组件的数据"></my-component>
</div>
<script>
Vue.component('my-component',{
props:['message'],
template:'<div>{{message}}</div>'
})
var vm = new Vue({
el:'#app'
})
</script>
props中声明的数据与组件data函数return的数据主要区别就是,props来自父级,而data中的是组件自己的数据,作用域是组件本身,这两种数据都可以在模板template及计算属性computed和方法methods中使用。
上述代码中组件的message就是通过props从父级传递过来的,在组件的自定义标签上直接写该props的名称,如果要传递多个数据,在props数组中添加项即可。
ps:由于HTML特性不区分大小写,当使用DOM模板时,驼峰命名的props名称要转为短横分隔命名
有时,需要接收来自父级的动态数据,这时可以通过v-bind来动态绑定props的值,当父组件的数据变化时,也会传递给子组件:
<div id="app">
在父组件上输入:<input type="text" v-model="parentMessage" />
<my-component :message="parentMessage"></my-component>
</div>
<script>
Vue.component('my-component',{
props:['message'],
template:'<div>这里是子组件接收的值:{{message}}</div>'
})
var vm = new Vue({
el:'#app',
data:{
parentMessage:''
}
})
</script>
如果想向组件传递数组、对象、布尔值、数字,则必须用v-bind进行绑定,否则这些内容将会被当成字符串进行传递。
单向数据流
Vue 2.x 通过props传递数据都是单向的了,父组件数据变化时会传递给子组件,但反过来不行。这样的目的是将父子组件解耦,防止互相干扰出现不必要的情况。
业务中会经常遇到两种需要改变prop的情况,一种是父组件传递初始值进来,子组件将其作为初始值保存起来,在自己的作用域下任意使用和修改:
<div id="app">
<my-component :init-count="1"></my-component>
</div>
<script>
Vue.component('my-component',{
props:['initCount'],
template:'<div>{{count}}</div>',
data(){
return {
count:this.initCount
}
}
})
var vm = new Vue({
el:'#app'
})
</script>
组件中声明了数据count,它在组件初始化时会获取来自父组件的 initCount ,之后就与之无关了,我们秩序维护count,这样就可以避免直接操作 initCount
另一种情况就是prop作为需要被转变的原始值传入,用计算属性即可:
<div id="app">
<my-component :width="100"></my-component>
</div>
<script>
Vue.component('my-component',{
props:['width'],
template:'<div :style="style">组件内容</div>',
computed:{
style(){
return this.width+'px'
}
}
})
var vm = new Vue({
el:'#app'
})
</script>
数据验证
当prop需要验证时,需要采用对象写法:
Vue.component('my-component',{
props:{
propA:Number, //必须是数字类型
propB:[String,Number], //必须是字符串或数字类型
propC:{
type:Boolean, //布尔值,若没定义则默认值是true
default:true
},
propD:{
trye:Number, //数字,而且是必传
required:true
},
propE:{
type:Array, //如果是数组或对象,默认值必须是一个函数来返回
default(){
return [];
}
},
propF:{ //自定义一个验证函数
validator(value){
return value>10
}
}
}
})
校验的type类型可以是:
- String
- Number
- Boolean
- Object
- Array
- Function
type也可以是一个自定义构造器,使用instanceof检测
当prop验证失败时,在开发版本下会向控制台抛出一条警告
组件通信
父组件要向子组件通信,通过props传值即可,但Vue组件通信场景不止只有这样一种,归纳起来可用下图表示:
组件关系可分为父子组件通信、兄弟组件通信、跨级组件通信
自定义事件
当子组件需要向父组件传递数据时,就需要用到自定义事件。
v-on除了监听DOM事件外,还可以用于组件之间的自定义事件。
Vue组件中,子组件用$emit( )来触发事件,父组件用$on( )来监听子组件的事件
父组件也可以直接在子组件的自定义标签上使用v-on来监听子组件触发的自定义事件:
<div id="app">
{{total}}
<my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component>
</div>
<template id="example">
<div>
<button @click="handleIncrease">+1</button>
<button @click="handleReduce">-1</button>
</div>
</template>
<script>
Vue.component('my-component',{
template:example,
data(){
return {
counter:0
}
},
methods:{
handleIncrease(){
this.counter++;
this.$emit('increase',this.counter);
},
handleReduce(){
this.counter--;
this.$emit('reduce',this.counter);
}
}
})
var vm = new Vue({
el:'#app',
data:{
total:0
},
methods:{
handleGetTotal(total){
this.total = total;
}
}
})
</script>
上述例子中,子组件里定义了两个分别具有+1和-1功能的按钮,在调用对应的方法时,通过$emit( )再将数据传递给父组件,父组件用@increase 和@reduce。
$emit( )方法的第一个参数是自定义事件的名称,后面的参数是用来传递的数据
除了用v-on在组件上监听自定义事件外,还可以监听DOM事件,这时可用.nativ修饰符表示监听的是一个原生事件,监听的是该组件的根元素:
<my-component @click.native="handleClick"></my-component>
使用v-model
Vue 2.x 可在自定义组件上使用v-model指令:
<div id="app">
{{total}}
<my-component v-model="total"></my-component>
</div>
<template id="example">
<div>
<button @click="handleIncrease">+1</button>
<button @click="handleReduce">-1</button>
</div>
</template>
<script>
Vue.component('my-component',{
template:example,
data(){
return {
counter:0
}
},
methods:{
handleIncrease(){
this.counter++;
this.$emit('input',this.counter);
},
handleReduce(){
this.counter--;
this.$emit('input',this.counter);
}
}
})
var vm = new Vue({
el:'#app',
data:{
total:0
}
})
</script>
这里和上面用自定义事件实现的效果一样,只不过这里子组件中的$emit( )中返回的事件名是一个特殊的 input ,这样也算是v-model的一个语法糖,简化了写法。
非父子组件通信
非父子组件一般有两种,兄弟组件和跨多级组件。
在Vue.js 2.x中,一般使用一个空的Vue实例作为中央事件总线(bus),也就是作为数据传递的中介:
<div id="app">
{{message}}
<my-component></my-component>
</div>
<template id="example">
<div>
<button @click="handleEvent">传递事件</button>
</div>
</template>
<script>
var bus = new Vue(); //定义一个空Vue实例作为bus
Vue.component('my-component',{
template:example,
methods:{
handleEvent(){
//向bus发送事件
bus.$emit('on-message','来自组件 my-component 的内容')
}
}
})
var vm = new Vue({
el:'#app',
data:{
message:''
},
mounted() {
var _this = this;
//在实例初始化时,监听来自bus实例的事件
bus.$on('on-message',function(msg){
_this.message = msg;
})
}
})
</script>
上述代码中,首先创建了一个名为bus的空Vue实例,我们将其作为中介使用。然后全局定义了一个组件my-component,最后创建Vue实例vm,在vm初始化时,通过钩子函数mounted开启了对bus事件 on-message 的监听,而在组件
my-component 中,点击按钮会通过bus将事件 on-message 发送出去,此时vm就会接收到来自bus的事件,进而在回调中完成自己的业务逻辑。
使用Slot分发内容
当需要让组件组合使用,混合父组件的内容与子组件的模板时,就会用到slot,这个过程叫做内容分发。
props 传递数据、events 触发事件和slot 内容分发就构成了Vue组件的3个API来源,再复杂的组件也是由这三部分构成。
作用域
例如,在父组件中有如下模板:
<div id="app">
<my-component>{{message}}</my-component>
</div>
如上,message 实际上就是一个slot,但它绑定的是父组件data中的message数据,而不是组件<my-component>的数据
父组件模板的内容是在父组件的作用域内编译,子组件模板的内容是在子组件作用域内编译:
<div id="app">
<my-component></my-component>
</div>
<template id="example">
<div v-show="isShow"></div>
</template>
<script>
Vue.component('my-component',{
template:example,
data(){
return {
isShow:true
}
}
})
var vm = new Vue({
el:'#app'
})
</script>
如上,此时,写在组件模板中的isShow绑定的便是对应组件内的data
slot用法
在子组件内使用特殊的<slot>元素就可以为这个子组件开启一个slot(插槽),在父组件模板里,插入在子组件标签内的所有内容将替代子组件的<slot> 标签及它的内容:
<div id="app">
<my-component>
<p>插入的内容</p>
<p>更多插入的内容</p>
</my-component>
</div>
<template id="example">
<div>
<slot>
<p>如果没插入内容,则会显示这行内容</p>
</slot>
</div>
</template>
<script>
Vue.component('my-component',{
template:example
})
var vm = new Vue({
el:'#app'
})
</script>
子组件my-component的模板内定义了一个slot元素,并用一个<p>作为默认的内容,且此处的默认内容作用域是子组件本身;如果写入了slot,则会替换整个<slot>,故渲染后的结果是:
<div id="app">
<div>
<p>插入的内容</p>
<p>更多插入的内容</p>
<div>
</div>
具名Slot
给<slot>元素指定一个name后可以分发多个内容,具名Slot可以与单个Slot共存:
<div id="app">
<my-component>
<h2 slot="header">标题</h2>
<p>正文内容</p>
<p>更多的正文内容</p>
<div slot="footer">底部信息</div>
</my-component>
</div>
<template id="example">
<div class="container">
<div class="header">
<slot name="header"></slot>
</div>
<div class="main">
<slot></slot>
</div>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>
</template>
上述代码中,组件模板内声明了3个<slot>元素,其中<div class="main">内的<slot>没有使用name特性,故它将作为默认slot使用,父组件没有使用slot特性的元素和内容都将出现在这里。
如果没有指定默认的匿名slot,父组件内多余的内容片段都将被抛弃。
Comments 1 条评论