vue3组件封装的思想与实践

7 阅读4分钟

前言:在使用vuejs的过程中,我们几乎每天都写组件。但是在业务开发的过程中,我们会发现即使是不同功能的组件,里面也会包含很多的相似代码。我们会将大部分精力投入到相似代码之中,这样会大幅度降低我们的开发效率。这个时候,将这些具备相同功能的代码独立封装成一个组件可以减少开发时间,也可以降低后续维护的难度。

为什么需要封装组件

组件封装的本质,是控制复杂度。试想一下,一个页面具备多个含有loading的按钮,多个表单校验,多个弹窗,这是否会导致重复代码量的提升?是否会导致这个组件的代码行数的增多?这会导致我们在编写和维护变得困难。

<button class="btn-primary" @click="submit" :disabled="loading">
  {{ loading ? '提交中...' : '提交' }}
</button>

当这种结构在多个页面出现时,一旦样式或交互规则改变,你需要修改所有地方。 这时,“复制”不是解决方案,“抽象”才是。

如何封装一个“好”的组件

在我的工作实践中,我总结出了以下几点。

  1. 单一职责:一个组件应该专注于一件事情,不能让一个组件既负责UI展示,又负责复杂业务计算。组件应该保持功能的纯粹。
  2. 高内聚,低耦合:组件应该保持自身的独立性,尽可能地把组件应该处理交给组件自身处理。组件内部逻辑自洽,对外只暴露必要接口。
  3. 保持单一数据流:组件内部使用pros接收父组件输入,使用emit向父组件输出数据。子组件要严格保有父组件传入的数据内容,不能直接修改,而是通过事件通知父组件自身修改。
  4. 可配置:通过 props 控制:类型、状态、行为。而不是把逻辑写在内部无法扩展。
  5. 可组合:可以使用slots让组件更加灵活。

从混乱到抽象:一个按钮组件的演进

我们通过一个简单例子,看看封装是如何一步步完成的。

第一步:页面中重复代码

<button
  class="btn-primary"
  :disabled="loading"
  @click="submit"
>
  {{ loading ? '提交中...' : '提交' }}
</button>

第二步:抽离基础组件

创建 BaseButton.vue

<template>
  <button
    :class="['btn', `btn-${type}`]"
    :disabled="disabled || loading"
    @click="handleClick"
  >
    <slot />
  </button>
</template>

<script setup>
const props = defineProps({
  type: {
    type: String,
    default: 'primary'
  },
  disabled: Boolean,
  loading: Boolean
})

const emit = defineEmits(['click'])

function handleClick(e) {
  if (!props.loading && !props.disabled) {
    emit('click', e)
  }
}
</script>

第三步:外部使用

<BaseButton
  type="primary"
  :loading="loading"
  @click="submit"
>
  提交
</BaseButton>

封装的过程,其实就是:

不断识别重复 → 提取抽象 → 明确边界 → 提供接口。

组件类型的区分:不要过度抽象

并不是所有组件都应该做成“高度通用”。

我们可以大致分为三类:

1️⃣ 基础组件

例如:

  • Button
  • Input
  • Modal

高复用、高抽象。


2️⃣ 业务组件

例如:

  • 订单卡片
  • 用户信息模块

与业务强绑定,不必追求极致通用。


3️⃣ 容器组件

负责:

  • 数据请求
  • 状态管理
  • 业务组合

尽量不承担具体 UI 展示。


抽象过度同样是一种设计错误。

如果一个组件的 props 多到难以理解,那说明它可能承担了过多职责。

常见错误与反模式

在实际开发中,常见的问题包括:

  • props 数量过多
  • 子组件直接修改父组件数据
  • 组件过度拆分导致维护成本增加
  • 在 template 中堆积复杂逻辑
  • 滥用 provide / inject

组件封装不是“拆得越碎越好”,而是“职责越清晰越好”。

总结:封装的核心是边界

Vue3 提供了更强的表达能力,但 API 只是工具。

真正决定组件质量的,是对“边界”的理解。

一个优秀的组件应该:

  • 内部逻辑封闭
  • 对外接口清晰
  • 数据流向明确
  • 结构可组合
  • 行为可配置

从混乱到优雅,并不是一蹴而就。

它来自于:

  • 对重复代码的敏感
  • 对复杂度的警惕
  • 对抽象层级的把握

这,才是组件封装真正的意义。

但是在工作中,为了提升效率,大部分情况是对已有基础组件进行二次封装,要避免重复造轮子。