出处:掘金

原作者:金泽宸


组件不是“能用”就行,而是“能复用、能扩展、能插拔”才算架构师级别

写在前面

在前端开发中,组件是最小的交互单元,也是系统构建的基石。 架构师如果不能设计出一套“可维护 + 可插拔 + 可复用 + 可测试”的组件体系,项目的复杂度会以指数级增长

这一篇,我们将深入解析高质量组件设计的三大核心模式,并通过多个实战案例拆解:

  • 可复用(Reusable)
  • 可扩展(Extensible)
  • 可插拔(Pluggable)

可复用组件设计:让你的组件不止用一次

原则 1:数据驱动优先,props 替代硬编码

  • ❌ 不可复用组件(写死 UI,一梭子就是干!)
<!-- Bad: Modal.vue -->
<template>
  <div class="modal">
    <h3>确认删除?</h3>
    <p>删除后不可恢复</p>
    <button @click="confirm">确定</button>
  </div>
</template>
  • ✅ 改造后(props 控制内容,经典常用)
<!-- Good: Modal.vue -->
<template>
  <div class="modal">
    <h3>{{ title }}</h3>
    <p>{{ description }}</p>
    <slot name="footer">
      <button @click="onConfirm">确定</button>
    </slot>
  </div>
</template>
 
<script setup>
defineProps({
  title: String,
  description: String,
  onConfirm: Function,
})
</script>

使用方式:

<Modal title="确认删除" description="删除后不可恢复" :onConfirm="deleteUser">
  <template #footer>
    <pf-button type="danger" @click="deleteUser">确认删除</pf-button>
  </template>
</Modal>

可扩展组件设计:对变化保持开放

目标:当业务需求变化时,组件不被迫大改,而是通过扩展点实现新增功能

方法 1:暴露 slot 插槽

<!-- DataTable.vue -->
<template>
  <table>
    <thead>
      <tr>
        <th v-for="col in columns">{{ col.label }}</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="row in data">
        <td v-for="col in columns">
          <slot :name="col.prop" :row="row">
            {{ row[col.prop] }}
          </slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

使用方式:

<DataTable :columns="[{ prop: 'name', label: '名称' }]">
  <template #name="{ row }">
    <span style="color: red">{{ row.name }}</span>
  </template>
</DataTable>

优势:

  • 默认渲染走组件内逻辑
  • 业务方可插入任意结构,无需修改组件源码

方法 2:组件内部暴露“扩展点 API”

// useTable.ts
export function useTable({ fetchFn, transformFn }) {
  const loading = ref(false)
  const data = ref([])
 
  async function load() {
    loading.value = true
    const res = await fetchFn()
    data.value = transformFn ? transformFn(res) : res
    loading.value = false
  }
 
  onMounted(load)
 
  return { data, loading, reload: load }
}

外部传入扩展逻辑:

const { data, loading } = useTable({
  fetchFn: fetchUserList,
  transformFn: (res) => res.map((i) => ({ ...i, label: i.name })),
})

组件变得更加灵活、组合性更强

可插拔组件设计:打造“系统级能力”

让业务开发者不改动代码,只配置/注入,即可使用组件新能力

示例 1:按钮 + 权限系统

<!-- PermissionButton.vue -->
<template>
  <slot v-if="hasPermission"></slot>
</template>
 
<script setup>
import { usePermission } from '@/shared/hooks/usePermission'
const props = defineProps({ code: String })
const hasPermission = usePermission(props.code)
</script>

使用方式:

<PermissionButton code="user.delete">
  <button @click="deleteUser">删除用户</button>
</PermissionButton>

组件“无侵入”支持权限逻辑,支持任意业务接入

示例 2:动态组件注册机制

// registerComponents.ts
import { app } from 'vue'
import ChartCard from './ChartCard.vue'
import TableCard from './TableCard.vue'
 
export const registerDynamicComponents = (app) => {
  app.component('ChartCard', ChartCard)
  app.component('TableCard', TableCard)
}

平台配置:

[
  { "type": "ChartCard", "data": { "title": "访客趋势" } },
  { "type": "TableCard", "data": { "columns": [...], "data": [...] } }
]

渲染引擎:

<component
  v-for="item in configList"
  :key="item.type"
  :is="item.type"
  v-bind="item.data"
/>

用户只需配置 JSON,即可渲染页面

组件设计规范建议表

项目建议
命名规范统一 PascalCase,如 UserCard
props 设计支持 default、可组合、类型明确
插槽设计命名清晰:#header#footer#cell-{column}
事件命名使用 update:onXXX 标准
代码拆分分离逻辑 hooks、展示 UI、样式模块
性能优化v-memodefineOptions({ inheritAttrs: false })

实战组件封装案例建议

  1. 通用弹窗服务(useDialog + Modal 插槽)
  2. 表格系统封装(动态列配置 + 权限按钮 + 嵌套子表格)
  3. 上传系统(支持文件/图片/压缩包 + 自定义校验 + 后缀限制)
  4. 表单系统(JSON Schema 驱动 + 自定义校验规则 + 动态联动)
  5. 图表系统(ECharts 容器 + 响应式封装 + 动态配置渲染)