跳转到内容

Vue3 + TS 二次封装组件库组件

刀刀

6/19/2025

0 字

0 分钟

思考

如何更好的封装一个组件库的组件呢?主要从以下三个方面考虑:

  • props 如何穿透过去
  • 插槽 如何穿透过去
  • 组件内部的方法如何暴露出去

props穿透

props 穿透可以写成以下的方式:

vue
<template>
  <Son></Son>
</template>
vue
<template>
  <el-input v-bind="$attrs"></el-input>
</template>

这么做虽然可以穿透事件与方法,但是父组件没有代码提示,只能被迫手敲或者翻阅文档,这样做不利于开发。因此需要转换思路。

Element Plus 组件库导出提供了相关的组件类型,我们可以借助这个类型来获取代码提示。
vue
<script lang="ts" setup> 
import { type InputProps } from 'element-plus'
const props = defineProps<InputProps>() 
</script> 

<template>
  <el-input v-bind="$attrs"></el-input> 
  <el-input v-bind="props"></el-input> 
</template>

现在父组件使用时能看到相应的提示了,但是出现了 TypeScript 的报错,提示参数是必传的,需要使用 TypeScriptPartial 类型来包裹一下。 Partial 作用是将类型中的所有属性变为可选。

目前只考虑了属性,还没考虑到事件。事件都在 $attrs 中,因此可以用浅拷贝的方式把 $attrsprops 合并一下。

vue
<script lang="ts" setup>
import { type InputProps } from 'element-plus'
const props = defineProps<InputProps>() 
const props = defineProps<Partial<InputProps>>() 
</script>

<template>
  <el-input v-bind="props"></el-input> 
  <el-input v-bind="{ ...$attrs, ...props }"></el-input> 
</template>

插槽穿透

插槽写法可以 v-for 循环 $slots ,循环把插槽挂载到子组件上,但是这么做很繁琐。

可以转变一下思路,不直接使用 <el-input></el-input> 的方式挂载组件,而是通过 <component :is=""></component> 的形式挂载组件。is 不仅可以给一个子组件,还可以给一个 h 函数,因此可以借助 h 函数来挂载插槽。

h 函数第一个参数是组件,第二个参数是属性,第三个参数是插槽。

vue
<script lang="ts" setup>
import { ElInput, type InputProps } from 'element-plus'
import { h } from 'vue'
const props = defineProps<Partial<InputProps>>()
</script>

<template>
  <el-input v-bind="{ ...$attrs, ...props }"></el-input> 
  <component :is="h(ElInput, { ...$attrs, ...props }, $slots)"></component> 
</template>

组件方法暴露

组件方法暴露可以使用 ref 来暴露方法。

vue
<script lang="ts" setup>
import { ElInput, type InputProps } from 'element-plus'
import { h, ref } from 'vue'
const props = defineProps<Partial<InputProps>>()

const inputRef = ref() 
console.log(inputRef.value) 
</script>

<template>
  <component :is="h(ElInput, { ...$attrs, ...props, ref: 'inputRef' }, $slots)"></component> 
</template>

但是这种方法不可取,如果这个组件绑定了 v-if ,后续存在可能会变动的情况,这样就拿不到 inputRef 的值了。ref 不仅可以赋值字符串,但是还能赋值一个函数,函数的形参接收的就是组件实例。

那么怎么抛出去呢?Vue3 提供了 getCurrentInstance() 方法实例,提供了一个 exposed 方法可以暴露组件实例。因此上方代码可以改写为:

vue
<template>
  <Son model-value="111">
    <template #prefix></template>
  </Son>
</template>
vue
<script lang="ts" setup>
import { ElInput, type InputProps } from 'element-plus'
import { h, getCurrentInstance } from 'vue'
const props = defineProps<Partial<InputProps>>()

const vm = getCurrentInstance() 
function changeRef(inputInstance) { 
  vm.exposed = vm.exposeProxy = inputInstance || {} 
} 
</script>

<template>
  <component :is="h(ElInput, { ...$attrs, ...props, ref: changeRef }, $slots)"></component> 
</template>

注意

父组件拿到的不是直接拿 exposed,而是 exposedexposeProxy 代理对象属性,因此不能只修改 vm.exposed ,还需要修改 vm.exposeProxy