组件的二次封装
在项目开发中,UI 设计可能会对某些组件的结构进行改动,而这些改动通常无法通过 CSS 简单调整实现。这种情况下,我们需要对组件进行二次封装。
在二次封装时,不仅要加入业务所需的代码逻辑,还需要保留原有组件的 props
、emits
、slots
和 methods
等功能,以确保组件的原有特性不受影响。
例如,UI 设计将 el-select
组件右侧的箭头替换为自定义图标。通过组件的 suffix-icon
属性可以设置图标,但如果项目中所有的下拉选择框都需要统一的样式,我们不可能在每次使用 el-select
组件时都手动设置 suffix-icon
。此时,可以对 el-select
组件进行二次封装,并在封装后的组件中统一设置 suffix-icon
。
示例代码
结构目录
text
src
└── components
└── IElSelect
├── src
│ └── IElSelect.vue
└── index.ts
IElSelect.vue
vue
<script setup lang="tsx" name="IElSelect">
import { ref } from 'vue'
import { ElSelect } from 'element-plus'
// 使用 defineSlots 来处理 $slots,避免 TS 类型报错
const slots = defineSlots<InstanceType<typeof ElSelect>['$slots']>()
// 引用原始组件实例
const selectRef = ref()
// 暴露组件实例中的所有方法
defineExpose(
new Proxy(
{},
{
get(_, key) {
return selectRef.value?.[key]
},
has(_, key) {
return key in selectRef.value
}
}
)
)
// 这里可以根据业务需求自定义
const SuffixIcon = () => {
return <CustomIcon icon="arrow-down-fill"></CustomIcon>
}
</script>
<template>
<el-select
ref="selectRef"
:suffix-icon="SuffixIcon"
v-bind="$attrs"
>
<!-- 转发 el-select 的所有插槽 -->
<template
v-for="(_, slot) in slots"
v-slot:[slot]="slotProps"
:key="slot"
>
<slot
:name="slot"
v-bind="slotProps"
></slot>
</template>
</el-select>
</template>
通过上述封装,组件功能已完成。不过,直接使用该组件时,代码编辑器可能无法提供代码提示。为了让编辑器支持类型提示,我们需要在 index.ts
中为封装后的组件声明类型。
index.ts
ts
import { ElSelect } from 'element-plus'
import IElSelect from './src/IElSelect.vue'
// 将类型指向 el-select,保留原有类型提示
export default IElSelect as typeof ElSelect & typeof IElSelect
使用方法
在需要使用的地方引入封装后的组件,编辑器即可提供完整的类型提示,使用方法与 el-select
的使用方法一致。
ts
import IElSelect from '@/components/IElSelect'
通过这样的二次封装,既实现了业务需求,也保留了组件原有的功能和代码提示,提高了代码的规范性和可维护性。