🍐 Vue3 优雅的二次封装组件!

471 阅读2分钟

大家好,我是 前端架构师 - 大卫

更多优质内容请关注微信公众号 @程序员大卫

初心为助前端人🚀,进阶路上共星辰✨,

您的点赞👍与关注❤️,是我笔耕不辍的灯💡。

背景

在实际开发中,我们常常基于成熟的 UI 组件库(如 Element Plus)进行二次封装,以统一交互风格、增强复用性或加入业务逻辑。本文将通过具体示例,逐步演示如何封装一个带有插槽的输入框组件,并介绍两种常见的插槽处理方式。

直接使用组件库的组件

我们先从最基础的使用方式开始,假设我们已经封装了一个 ElInput 组件,模拟了 Element Plus 的 Input 功能,并使用了 v-model 与命名插槽。

1. 创建 ElInput 组件

<!-- ElInput.vue -->
<script setup lang="ts">
const model = defineModel();
</script>

<template>
  <slot name="prefix" msg="hello"></slot>
  <div><input v-model="model" /></div>
  <slot name="suffix" msg="world"></slot>
</template>

这个组件暴露了两个插槽:prefixsuffix,并将一些数据(如 msg)通过 slot props 传递给外部。

2. 使用该组件

<!-- App.vue -->
<script setup lang="ts">
import { ref } from "vue";
import ElInput from "./components/ElInput.vue";

const inputText = ref("");
</script>

<template>
  <ElInput v-model="inputText">
    <template #prefix="props">前缀: {{ props.msg }}</template>
    <template #suffix="props">后缀: {{ props.msg }}</template>
  </ElInput>
</template>

结果如下图所示:

插槽(slot)是 Vue 中用于内容分发的重要机制。推荐阅读 Vue 插槽官方文档 以深入了解。

二次封装组件的两种方式

如果我们希望在中间再封装一层组件(例如 ElInputWrap.vue),并将插槽“透传”到内部组件,我们可以通过以下两种方式实现:

方法一:通过遍历 $slots 实现插槽透传

此时 App.vue 不再直接使用 ElInput,而是通过中间组件 ElInputWrap 进行调用。我们注意到,先前使用时存在两个具名插槽 prefixsuffix,目标是在 ElInputWrap 中通过遍历 $slots,动态地将这些插槽渲染出来,并通过 <slot> 标签完成内容透传。

<!-- ElInputWrap.vue -->
<template>
  <div>
    <h1>我是二次封装的组件</h1>
    <ElInput>
      <template v-for="(_, slotName) in $slots" #[slotName]="props">
        <slot :name="slotName" v-bind="props" />
      </template>
    </ElInput>
  </div>
</template>

<script setup lang="ts">
import ElInput from "./ElInput.vue";

// 提取插槽类型以获得更好的 TS 支持
type ElInputSlots = InstanceType<typeof ElInput>["$slots"];
defineSlots<ElInputSlots>();
</script>

这种方式通过遍历 $slots 动态渲染命名插槽,实现了插槽的“透传”。它简单直观,适合大多数场景。

方法二:使用 componenth 函数组合动态渲染

h 函数会创建一个虚拟 DOM 节点(VNode),而 <component :is="..."> 可以接收这个 VNode,进而动态渲染对应的组件。

这是因为 Vue 3 中的 <component> 实际上会解析 is 属性传入的值,无论是组件构造、字符串标签,还是 VNode,都能动态处理并正确挂载。这样做的好处是,你可以通过 h() 动态组合组件的类型、props 和插槽,然后统一交给 <component> 渲染,实现更灵活的组件封装方式。

<!-- ElInputWrap.vue -->
<template>
  <div>
    <h1>我是二次封装的组件</h1>
    <component :is="h(ElInput, $attrs, $slots)"></component>
  </div>
</template>

<script setup lang="ts">
import { h } from "vue";
import ElInput from "./ElInput.vue";

// 从 ElInput 的类型中提取插槽类型
type ElInputSlots = InstanceType<typeof ElInput>["$slots"];

// 定义 ElInputWrap 的插槽类型与 ElInput 一致
defineSlots<ElInputSlots>();
</script>

为什么要这样做?

  • h 函数可以让你动态构造组件的 VNode,并手动注入 propsattrsslots
  • <component :is="..."> 支持动态组件加载;
  • 当你需要进一步“控制渲染逻辑”或做更底层的封装(例如和渲染函数结合)时,这种方式更具灵活性。

官方文档:componenth

总结

本文介绍了如何通过两种方式对组件库中的组件进行二次封装,重点解决了“插槽透传”的问题:

  • 遍历$slots 简单直观,适用于大多数命名插槽场景;
  • **使用h + component:**适用于更灵活、更底层的封装需求。

在实际项目中,我们建议优先使用第一种方式,清晰、易维护;在遇到复杂需求或需要动态渲染组件时,可考虑第二种方式。