一、前置场景
前端小伙伴在开发过程中,避免不了使用第三方 UI 组件库快速进行迭代开发,比如在 PC 端 vue 项目中,使用Element UI
,对于大部分童鞋来说那是手到擒来的事情吧?但是由于业务需求千奇百怪,通过第三方组件库的拿来主义
偶然会捉襟见肘的,况且这个需求是一个复用性比较高的,那么此时我们就需要考虑进行组件封装,才能达到一劳永逸的效果!
二、案例举证
业务需求:有一个导入物流信息的功能按钮,而这个功能又区分维度,比如
按发货单导入、按FBA货件导入、按物流信息编码导入
等。如果把这个三个功能分别用 3 个按钮来实现,完全没有问题,但是产品提出了一个更好的建议,把相关功能按钮通过一个按钮选项来实现,让用户去选择操作。
三、组件封装实现方案
1.第一种方案
a. 封装的组件MoreSelect.vue
如下(vue + js 版本):
<template>
<el-select @change="handleChange" :placeholder="placeholder" value="" :size="size">
<el-option v-for="item in list" :key="item.value" :value="item">
{{ item.name }}
</el-option>
</el-select>
</template>
<script>
export default {
name: 'MoreSelect',
props: {
size: {
type: String,
default: 'medium',
},
placeholder: {
type: String,
default: '请选择',
},
list: {
type: Array,
default: [],
},
},
methods: {
handleChange(e) {
this.$emit('handleChange', e)
},
},
}
</script>
b. 封装的组件MoreSelect.vue
如下(vue + ts 版本):
<template>
<el-select @change="handleChange" :placeholder="placeholder" value="" :size="size">
<el-option v-for="item in list" :key="item.value" :value="item">
{{ item.name }}
</el-option>
</el-select>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
interface IOption {
name: string
value: string
}
@Component({
name: 'MoreSelect',
})
export default class MoreSelect extends Vue {
@Prop({ type: String, default: 'medium' }) size!: 'medium' | 'small' | 'mini'
@Prop({ type: String, default: '请选择' }) placeholder!: string
@Prop({ type: Array, default: [] }) list!: Array<IOption>
handleChange(e: IOption) {
this.$emit('handleChange', e)
}
}
</script>
在 template 模版里面使用如下:
<MoreSelect
size="small"
:list="[
{ name: '按发货单导入', value: '1' },
{ name: '按FBA货件导入', value: '2' },
{ name: '按物流信息编码导入', value: '3' },
]"
placeholder="导入物流信息"
@handleChange="handleChange"
></MoreSelect>
el-select 上面绑定value="",避免开发环境报错
到此一个组件已经完美封装完成啦!!!!!!!
2.第二种方案
基于第一种方案,我们在封装的组件中重新定义了一系列的属性 props(
size、placeholder、list
)和事件 event(handleChange
),然后再传给 elementUI 的组件 el-select,那么我们能否不定义属性和事件直接使用 el-select 的属性和事件呢 ?答案是可以的。
vue2.4.0版本新增的 2 个属性$attrs
和$listeners
能够完美解决此问题,也是优雅封装高阶组件的灵魂。 废话不说直接上代码如下:
a. 封装的组件MoreSelect.vue
如下(vue + js 版本):
<template>
<el-select v-bind="$attrs" v-on="$listeners">
<el-option v-for="item in list" :key="item.value" :value="item">
{{ item.name }}
</el-option>
</el-select>
</template>
<script>
export default {
name: 'MoreSelect',
// false:禁止属性绑定到跟元素上,默认为 true
inheritAttrs: false,
props: {
list: {
type: Array,
default: [],
},
},
}
</script>
b. 封装的组件MoreSelect.vue
如下(vue + ts 版本):
<template>
<el-select v-bind="$attrs" v-on="$listeners">
<el-option v-for="item in list" :key="item.value" :value="item">
{{ item.name }}
</el-option>
</el-select>
</template>
<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
interface IOption {
name: string
value: string
}
@Component({
name: 'MoreSelect',
inheritAttrs: false,
})
export default class MoreSelect extends Vue {
@Prop({ type: Array, default: [] }) list!: Array<IOption>
}
</script>
在 template 模版里面使用如下:
// 注意此时的组件拥有el-select的所有属性和事件,无需再组件里面声明
<MoreSelect
size="small"
:list="[
{ name: '按发货单导入', value: '1' },
{ name: '按FBA货件导入', value: '2' },
{ name: '按物流信息编码导入', value: '3' },
]"
value=""
placeholder="导入物流信息"
@change="handleChange"
></MoreSelect>
四、vue2 是不是过时啦?俺也不太清楚,那么在 vue3
中如何使用呢?
vue3代码如下:
<template>
<el-select v-bind="attrs">
<el-option v-for="item in list" :value="item">{{ item.name }}</el-option>
</el-select>
</template>
<script lang="ts" setup>
import { useAttrs } from 'vue';
type IOption = {
name: string;
value: string;
};
const attrs = useAttrs();
defineProps({
list: {
type: Array<IOption>,
default: [],
},
value: String,
});
</script>