Skip to content

组件的二次封装

在项目开发中,UI 设计可能会对某些组件的结构进行改动,而这些改动通常无法通过 CSS 简单调整实现。这种情况下,我们需要对组件进行二次封装。

在二次封装时,不仅要加入业务所需的代码逻辑,还需要保留原有组件的 propsemitsslotsmethods 等功能,以确保组件的原有特性不受影响。

例如,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'

通过这样的二次封装,既实现了业务需求,也保留了组件原有的功能和代码提示,提高了代码的规范性和可维护性。