vue 怎么封装一个组件
一、组件基础:prop、slot、event
一个再复杂的组件,都是由三部分组成的:prop、event、slot,它们构成了 Vue.js 组件的 API。如果开发的是一个通用组件,那一定要事先设计好这三部分,因为组件一旦发布,后面再修改 API 就很困难了,使用者都是希望不断新增功能,修复 bug,而不是经常变更接口。如果你阅读别人写的组件,也可以从这三个部分展开,它们可以帮助你快速了解一个组件的所有功能。
属性 prop
prop(官方文档)定义了这个组件有哪些可配置的属性,组件的核心功能也都是它来确定的。 props 最好用对象的写法,这样可以针对每个属性设置类型、默认值或自定义校验属性的值。一个简单的按钮组件: <wy-button/>
<template>
<button :class="'wy-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>
使用组件:
<wy-button size="large"></wy-button>
<wy-button disabled></wy-button>
在使用组件时,也可以传入一些标准的 html 特性,比如 id、class:
<wy-button id="btn" class="btn-submit"></wy-button>
这样的 html 特性,在组件内的 <button>元素(根元素)上会继承,并不需要在 props 里再定义一遍。这个特性是默认支持的,如果不期望开启,在组件选项里配置 inheritAttrs: false就可以禁用。
插槽slot
插槽 slot(官方文档),它可以分发组件的内容,比如在上面的按钮组件中定义一个插槽:
<template>
<button :class="'wy-button-size' + size" :disabled="disabled">
<slot name="icon"></slot>
<slot>默认内容</slot>
</button>
</template>
使用:
<wy-button>
<template v-slot:icon>
<i class="submit"></i>
</template>
提交
</wy-button>
自定义事件
<template>
<button @click="handleClick">
</button>
</template>
<script>
export default {
methods: {
handleClick (event) {
this.$emit('on-click', event);
}
}
}
</script>
使用:
<wy-button @on-click="handleClick"></wy-button>
注意:如果不写 .native 修饰符,那么 @click 就是自定义事件 click,不是原生事件 click
自定义组件的v-model
v-model (官方文档)实际上可以说是props 结合event 的语法糖:组件内的<input> 默认需要提供value 属性的props 以及需要将新的值通过input 事件抛出。可通过model 选项改变默认值。
<template>
<input
type="text"
:value="value"
@input="$emit('input', $event.target.value)"
/>
</template>
<script>
export default {
props: {
value: String,
},
// 默认值
model: {
prop: 'value',
event: 'input',
},
}
</script>
使用:
<wy-input v-model="inputValue"></wy-input>
二、组件的通信 1:provide / inject
一般来说,组件之间的关系大概有以下几种:
A 和 B、B 和 C、B 和 D 都是父子关系,C 和 D 是兄弟关系,A 和 C 是隔代关系(可能隔多代)。组件间经常会通信,Vue.js 内置的通信手段一般有两种:
- ref : 给元素或组件注册引用信息.
- $parent / $children :访问父 / 子实例。
这两种都是直接得到组件实例,使用后可以直接调用组件的方法或访问数据。但是这两种方式的弊端就是无法跨级或者兄弟间通信。通常的解决方案是引入Vuex或者Bus的解决方案。但是在开发独立组件时是不可取的。
provide / inject
provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
虽然provide / inject 官方并不推荐使用在应用程序中,但是在很多组件库诸如iView以及ElementUi都有大量用,因此在开发独立组件 时还是非常好用的。
下面是一个基础用法。假设有两个组件: A.vue 和 B.vue,B 是 A 的子组件。
// A.vue
export default {
provide: {
name: 'zyh'
}
}
// B.vue
export default {
inject: ['name'],
mounted () {
console.log(this.name); // zyh
}
}
模拟Vuex的用法:把整个 app.vue 实例通过 provide 对外提供
<template>
<div>
<router-view></router-view>
</div>
</template>
<script>
export default {
provide () {
return {
app: this
}
}
}
</script>
把整个 app.vue 的实例 this 对外提供,命名为 app(这个名字可以自定义)。接下来,任何组件(或路由)只要通过 inject 注入 app.vue 的 app 的话,都可以直接通过 this.app.xx 来访问 app.vue 的 data、computed、methods 等内容。
独立组件使用 provide / inject 的场景,主要是具有联动关系的组件,比如下面要介绍的具有数据校验功能的表单组件 Form。它其实是两个组件,一个是 Form,一个是 FormItem, Form 和 FormItem 不一定是父子关系,中间很可能间隔了其它组件,所以不能单纯使用 $parent 来向上获取实例 。FormItem要取得Form 组件上的一些特性(props),所以就需要得到父组件 Form。
三、组件的通信 2:派发与广播—自定义实现dispatch和broadcast方法
provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。然后有两种场景它不能很好的解决:
-
父组件向子组件(支持跨级)传递数据
-
子组件向父组件(支持跨级)传递数据
因此要实现的dispatch和broadcast方法将具有以下功能:
-
在子组件调用 dispatch方法,向上级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该上级组件已预先通过 $on 监听了这个事件。
-
相反,在父组件调用 broadcast 方法,向下级指定的组件实例(最近的)上触发自定义事件,并传递数据,且该下级组件已预先通过 $on 监听了这个事件。
具体实现方式如下:假设有A.vue 和 B.vue 两个组件,其中 B 是 A 的子组件,中间可能跨多级,在 A 中向 B 通信:
<!-- A.vue -->
<template>
<button @click="handleClick">触发事件</button>
</template>
<script>
import Emitter from '../mixins/emitter.js';
export default {
name: 'componentA',
mixins: [ Emitter ],
methods: {
handleClick () {
this.broadcast('componentB', 'on-message', 'Hello Vue.js');
}
}
}
</script>
// B.vue
export default {
name: 'componentB',
created () {
this.$on('on-message', this.showMessage);
},
methods: {
showMessage (text) {
window.alert(text);
}
}
}
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
const name = child.$options.name;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
dispatch(componentName, eventName, params) {
let parent = this.$parent || this.$root;
let name = parent.$options.name;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.name;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
四、一个带有校验功能的Form组件
Form 组件的核心功能是数据校验,一个 Form 中包含了多个 FormItem,当点击提交按钮时,要逐一对每个 FormItem 内的表单组件校验,而校验是由使用者发起,并通过 Form 来调用每一个 FormItem 的验证方法,再将校验结果汇总后,通过 Form 返回出去。大致的流程如下图所示:
具体代码实现点击这里查看
浙公网安备 33010602011771号