vue组件三个API:prop、event、slot

基础:Vue.js 组件的三个 API:props、event、slot

我们自己写的vue组件,通常由三部分组成:props、event、slot,这就是我们自己编写组件的API。如果开发一个通用组件,那一定要事先设计好这三部分,因为组件一旦发布,后面再修改 API 就很困难了,使用者都是希望不断新增功能,修复 bug,而不是经常变更接口。如果你阅读别人写的组件,也可以从这三个部分展开,它们可以帮助你快速了解一个组件的所有功能。

属性props

vue 父子组件是通过props进行单向的数据传递,props定义了子组件可配置的属性。写通用组件props最好是使用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值。如果直接使用props数组的用法,这样往往不够严谨,下面是一个iView的一个按钮组件<i-button></i-button>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<template>
<button :class="'i-button-size'+size" :disabled="disabled"></button>
</template>
<script>
// 判断参数是否是其中之一
function oneOf(value,validList){
for (let i = 0; i < validList.length; i++) {
if (value === validList[i]) {
return true;
}
}
return false;
}
export default {
props: {
size:{
validator (value){
return oneOf(value, ['small', 'large', 'default']);
},
default: 'default'
},
disabled: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="less" scoped>
.i-button-sizesmall{}
.i-button-sizelarge{}
.i-button-sizedefault{}
</style>

使用组件:

1
2
<i-button size="large"></i-button>
<i-button disabled></i-button>

组件中定义了两个属性:尺寸 size 和 是否禁用 disabled。其中 size 使用 validator 进行了值的自定义验证,也就是说,从父级传入的 size,它的值必须是指定的 small、large、default 中的一个,默认值是 default,如果传入这三个以外的值,都会抛出一条警告。

要注意的是,组件里定义的 props,都是单向数据流,也就是只能通过父级修改,组件自己不能修改 props 的值,只能修改定义在 data 里的数据,非要修改,也是通过后面介绍的自定义事件通知父级,由父级来修改。

在使用组件时,也可以传入一些标准的 html 特性,比如 idclass

1
<i-button id="btn1" class="btn-submit"></i-button>

这样的 html 特性,在组件内的 <button> 元素上会继承,并不需要在 props 里再定义一遍。这个特性是默认支持的,如果不期望开启,在组件选项里配置 inheritAttrs: false 就可以禁用了。实例属性 $attrs可以让这些特性生效,且可以通过 v-bind 显性的绑定到非根元素上。

插槽 slot

如果我们需要自定义按钮<i-button>添加文字内容,就要用到插槽 slot,它可以分发组件的内容,比如在上面的按钮组件中定义一个插槽:

1
2
3
4
5
<template>
<button :class="'i-button-size' + size" :disabled="disabled">
<slot></slot>
</button>
</template>

使用:

1
2
3
4
<i-button>按钮 1</i-button>
<i-button>
<strong>按钮 2</strong>
</i-button>

当需要多个插槽时,会用到具名 slot:

1
2
3
4
5
6
<template>
<button :class="'i-button-size' + size" :disabled="disabled">
<slot name="icon"></slot>
<slot></slot>
</button>
</template>

1
2
3
4
<i-button>
<i-icon slot="icon" type="checkmark"></i-icon>
按钮 1
</i-button>

这样,父级内定义的内容,就会出现在组件对应的 slot 里,没有写名字的,就是默认的 slot。

自定义事件 event

给组件 <i-button> 加一个点击事件,我们先看自定义事件 event(部分代码省略):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<button @click="handleClick">
<slot></slot>
</button>
</template>
<script>
export default {
methods: {
handleClick (event) {
this.$emit('on-click', event);
}
}
}
</script>

通过 $emit,就可以触发自定义的事件 on-click ,在父级通过 @on-click 来监听:

1
<i-button @on-click="handleClick"></i-button>

上面的 click 事件,是在组件内部的 <button> 元素上声明的,这里还有另一种方法,直接在父级声明,但为了区分原生事件和自定义事件,要用到事件修饰符 .native,所以上面的示例也可以这样写:

1
<i-button @click.native="handleClick"></i-button>

如果不写 .native 修饰符,那上面的 @click 就是自定义事件 click,而非原生事件 click,但我们在组件内只触发了 on-click 事件,而不是 click,所以直接写 @click 会监听不到。

组件的通信

一般来说,组件可以有以下几种关系:

组件关系

A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。组件间经常会通信,Vue.js 内置的通信手段一般有两种:

  • ref:给元素或组件注册引用信息;
  • $parent / $children:访问父 / 子实例。

这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据,比如下面的示例中,用 ref 来访问组件(部分代码省略):

1
2
3
4
5
6
7
8
9
10
11
12
13
// component-a
export default {
data () {
return {
title: 'Vue.js'
}
},
methods: {
sayHello () {
window.alert('Hello');
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<template>
<component-a ref="comA"></component-a>
</template>
<script>
export default {
mounted () {
const comA = this.$refs.comA;
console.log(comA.title); // Vue.js
comA.sayHello(); // 弹窗
}
}
</script>

$parent$children 类似,也是基于当前上下文访问父组件或全部子组件的。

这两种方法的弊端是,无法在跨级兄弟间通信,比如下面的结构:

1
2
3
4
// parent.vue
<component-a></component-a>
<component-b></component-b>
<component-b></component-b>

我们想在 component-a 中,访问到引用它的页面中(这里就是 parent.vue)的两个 component-b 组件,那这种情况下,就得配置额外的插件或工具了,比如 Vuex 和 Bus 的解决方案

扩展阅读

-------------本文结束感谢您的阅读-------------