携手创作,一同成长!这是我参与「日新计划 8 月更文挑战」的第1天,点击检查活动详情>>
前语
常常的,咱们在日常工作中,会运用第三方UI组件库,比方:element-ui、vant-ui、iview、ant-design等等。不论是为了事务考虑仍是单纯的为了进步功率,咱们会把一些常常用到的组件抽离、封装成公共组件,这样便利咱们在不同的地方运用这个组件,减少重复代码的编写。
咱们把对于第三方组件库的封装称为组件的二次封装,那么这带来有个考虑,当咱们在二次封装时,咱们在封装什么?
二次封装时,咱们需求遵循什么?
在 vue 组件封装时,咱们需求留意的主要是三部分:prop、event、slot。
- prop:表示组件接纳的参数,最好用目标的写法,这样能够针对每个特点设置类型、默许值或自界说校验特点的值,此外还能够经过type、validator等方法对输入进行验证;
- event:子组件向父组件传递消息的重要途径;
- slot:能够给组件动态刺进一些内容或组件,是完结高阶组件的重要途径;当需求多个插槽时,能够运用具名slot。
你必须要知道的 $attrs
和 $listeners
咱们多级组件嵌套需求传递数据时,一般运用的方法是经过vuex。假如仅仅是传递数据,而不做中间处理,运用 vuex 处理,这就有点大材小用了。所以就有了 $attrs / $listeners
,一般合作 inheritAttrs 一同运用。
感觉仍是挺不流畅难明的,简略的说便是 inheritAttrs:true 承继除props之外的一切特点;inheritAttrs:false 只承继class特点。
-
$attrs
: 包含了父效果域中不被以为 (且不预期为) props 的特性绑定 (class 和 style 在外),而且能够经过v-bind="$attrs"
传入内部组件。当一个组件没有声明任何 props 时,它包含一切父效果域的绑定 (class 和 style 在外)。 -
$listeners
: 包含了父效果域中的 (不含 .native 修饰符) v-on 事情监听器。它能够经过v-on="$listeners"
传入内部组件。它是一个目标,里边包含了效果在这个组件上的一切事情监听器,相当于子组件承继了父组件的事情。
attrs和attrs 和 listeners 在做组件二次封装时非常有用。
怎么运用 $attrs
和 $listeners
上面说了那么多,咱们来看一个比如:
在运用 el-input-number时,当咱们给他赋默许值 null
或者空字符串 ""
时,会显现 0 ,而这在咱们一些事务场景里并不是很友爱,而且值是居中显现的,那么现在咱们想要做的改造是:值居左显现,没有默许值显现0的问题,且默许不展现操控按钮
- 操控按钮默许不显现:controls 设置成 false
- 居左显现:经过样式操控
- 默许值显现0的问题:经过 computed 计算不为 number 类型时,赋值为 undefined 解决
v-model 是一个语法糖,能够拆解为 props: value 和 events: input。便是说组件只需提供一个名为 value 的 prop,以及名为 input 的自界说事情
下面开始咱们对 el-input-number 的封装:
<template>
<el-input-number class="cz-input-number" :controls='controls' :value='num' @input="$emit('input',$event)"></el-input-number>
</template>
<script>
export default {
props: {
name: 'CzInputNumber',
value: [String, Number],
controls: {
type: Boolean,
default: false
}
},
computed: {
num() {
return typeof this.value === 'number' ? this.value : undefined
}
}
}
</script>
<style lang="scss" scoped>
.cz-input-number {
::v-deep .el-input__inner {
text-align: left;
}
}
</style>
上面 @input=”emit(′input′,emit(‘input’,event)” 的参数 $event 其实便是咱们在输入框输入的值,这是由于el-input-number 内部的 input 元素触发的是 “input” 自界说事情,而非原生 input 事情;此时现已是把值传递出来了,也便是 event.target.value,一切在自界说组件的回调函数中能够直接接纳这个 value 值。有兴趣的小伙伴能够去 element 对应的源码里看一下。(准确的来说是el-input 的源码,由于el-input-number 内部也是根据 el-input 的二次封装)
经过对 el-input-number 二次封装,咱们的需求其完成已基本完结了,可是咱们期望其用法坚持和 el-input-number 组件类似,这样即便其他人在运用咱们封装的组件时,也能参照 element 对应的文档正常将其运用起来。
二次封装尽量遵循的应该是原有根底的扩展,不论是为了针对事务仍是为了便利运用,而不是为组件重新制定一套新的用法,究竟封装的本质是为了进步运用体会,而不是添加更多不必要的心智担负。
这儿咱们就要用到上面介绍的 $attrs
和 $listeners
了
- $attrs “承继“ el-input-number 原有组件一切的 v-bind 特点
- $listeners “承继” el-input-number 原有组件一切 v-on 的事情
咱们为组件添加 v-bind="$attrs"
和 v-on="$listeners"
:
<template>
<el-input-number class="cz-input-number" v-bind="$attrs" v-on="$listeners" :controls='controls' :value='num' @input="$emit('input',$event)"></el-input-number>
</template>
<script>
export default {
name: 'CzInputNumber',
props: {
value: [String, Number],
controls: {
type: Boolean,
default: false
}
},
computed: {
num() {
return typeof this.value === 'number' ? this.value : undefined
}
}
}
</script>
<style lang="scss" scoped>
.cz-input-number {
::v-deep .el-input__inner {
text-align: left;
}
}
</style>
为了便利运用,对于常常运用的组件,我现已把他封装为了全局组件,所以直接经过 name 运用
<template>
<cz-input-number placeholder='请输入数量' @change="change" v-model="num"></cz-input-number>
</template>
<script>
export default {
data() {
return {
num: null
}
},
methods: {
change(val) {
console.log(val, typeof val)
}
}
}
</script>
效果:
能够看到,咱们传入初始值 null 时现已不会有默许显现 0 的情况了,没有在 props 里界说的 placeholder 经过 $attrs 的透传也收效了,再试验一下,change事情也能够正常触发。ok 高雅,咱们的对 el-input-number 的二次封装高雅完结。
一个考虑
已然咱们知道v-model是v-bind以及v-on合作运用的语法糖。那是不是咱们也能够运用 $attrs
和 $listeners
替咱们完结 v-model 呢,答案当然是能够的
假如上面咱们封装的 cz-number-input 不需求对初始值做处理,那么完全能够去掉 props中 value 的界说及 @input=”emit(′input′,emit(‘input’,event)” 的事情。
穿透一层组件完结 v-model
再次举个简略的比如,咱们期望 el-input 默许可清空,即clearable默以为ture,咱们根据其做二次封装如下:
<template>
<el-input v-bind='$attrs' v-on="$listeners" :clearable="clearable">
</el-input>
</template>
<script>
export default {
name:'CzInput',
props:{
clearable:{type:Boolean,default:true}
}
}
</script>
运用:
<template>
<CzInput placeholder="请输入内容" v-model="value"></CzInput>
</template>
<script>
export default {
data() {
return {
value: '默许可清空'
}
}
}
</script>
效果:
能够看到,咱们运用 CzInput 时,v-model 是完全没问题的,这儿咱们父组件是 CzInput ,穿透 el-input 这一层,抵达孙子原生 input 完结了v-model,并不需求重新界说 value 特点及 input 事情。
原因:父组件更改了数据,会由于
$atrrs
传递到子孙组件。而子孙组件 emit 一个 input 事情,会由于 $listeners 冒到父组件处。又由于父组件的 v-model 而主动把新数据赋值到父组件变量上,因此完结了所谓的”双向绑定”。
slot 插槽
上面咱们对 el-input 进行了简略的二次封装,所封装组件现已承继了 el-input 的一切特点及事情,可是 el-input 为了更便运用户自界说还提供了一系列插槽,所以咱们的封装也应该承继这些插槽。
一般插槽
<!-- 在组件中创立新的对应称号的插槽 -->
<template #slotName>
<!-- 在插槽内部运用对应称号的插槽 -->
<slot name="slotName" />
</template>
slotName 为咱们的插槽称号,默许插槽时称号为 default 或可不写
动态插槽
假如需求传递的slot不固定或者较多,咱们能够经过动态插槽称号透传
<template #[slotName] v-for="(slot, slotName) in $slots" >
<slot :name="slotName" />
</template>
这儿咱们把前面封装的 CzInput 加上插槽
<template>
<el-input v-bind='$attrs' v-on="$listeners" :clearable="clearable">
<template #[slotName] v-for="(slot, slotName) in $slots" >
<slot :name="slotName" />
</template>
</el-input>
</template>
<script>
export default {
name:'CzInput',
props:{
clearable:{type:Boolean,default:true}
}
}
</script>
咱们运用一下,为组件加一个后置的按钮:
<template>
<CzInput placeholder="请输入内容" v-model="value">
<el-button slot="append" icon="el-icon-search"></el-button>
</CzInput>
</template>
<script>
export default {
data() {
return {
value: '默许可清空'
}
}
}
</script>
效果图:
能够看到,插槽运用也是没有问题的
效果域插槽
假如需求封装组件运用了效果域插槽,咱们能够经过以下方法完结
<template #[slotName]="slotProps" v-for="(slot, slotName) in $slots" >
<slot :name="slotName" v-bind="slotProps"/>
</template>
具体运用示例能够看之前这篇文章里讲到的 vue 项目开发,我遇到了这些问题
小结
- 运用 $attrs 承继父组件的特点
- 运用 $listeners 承继父组件的事情
- 二次封装时插槽的传递
二次封装尽量遵循的应该是在原组件根底的扩展,不论是为了针对事务仍是为了便利运用,而不是为组件重新制定一套新的用法,究竟封装的本质是为了进步运用体会,而不是添加更多不必要的心智担负。
往期回顾
2022年了,我才开始学 typescript ,晚吗?(7.5k字总结)
vue 项目开发,我遇到了这些问题
关于首屏优化,我做了哪些