Skip to content

组件封装的一些思考

提示本文发布于 5 个月前,其中信息可能已经时过境迁...

做前端越久,越觉得"封装"这两个字说起来容易,做起来难。把功能写出来不难,但写成一个别人也能轻松用、改、扩的组件,又是另一回事。

最近整理笔记,把组件封装的一些经验沉淀了下来。

封装的几个维度

1. 通用性

组件不能太"业务"。如果一个组件里写死了某个接口的字段名、某个特定页面的样式,那它就不算一个通用组件。

我现在的习惯是:先抽象出"这个组件最核心的能力是什么",再围绕核心能力设计接口。业务相关的逻辑通过 props/slots 让调用方自己去处理。

2. API 清晰

一个好的组件,看 props 定义就应该知道怎么用。类型定义、默认值、注释,这些看似不起眼的东西,在多人协作的时候作用很大。

typescript
const props = withDefaults(defineProps<{
  // 数据源
  data: Array<any>;
  // 是否显示加载状态
  loading?: boolean;
  // 空数据时的提示文案
  emptyText?: string;
}>(), {
  loading: false,
  emptyText: '暂无数据',
});

3. 属性穿透

二次封装第三方组件时,最头疼的是怎么把属性和事件传过去。Vue 3 的 $attrs 可以自动把未声明的属性传递给子组件,但插槽和方法暴露就需要手动处理了。

插槽穿透可以用 h 函数:

vue
<template>
  <component :is="h(ElInput, $attrs, $slots)"></component>
</template>

方法暴露可以用 ref 回调的方式:

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

<script setup>
function exposeRef(exposed) {
  const instance = getCurrentInstance();
  instance.exposed = exposed;
}
</script>

这样二次封装的组件,对调用方来说是"透明的"——用起来和直接用原组件没什么区别。

封装的意义

封装不是为了炫技,而是为了"隔离复杂度"。调用方只需要知道"这个组件能做什么",不需要知道"它怎么做的"。

把复杂留给自己,把简单留给别人——这是我理解的封装精神。