组件库设计:组合与继承的核心策略

154 阅读3分钟

结合常见组件库(如 Ant Design、Element UI)中的组件,用具体例子说明组合(含聚合)和继承的使用场景,更直观理解两者的区别:

一、组合(含聚合)的典型例子

组合是组件库中最核心的组织方式,以下是高频场景:

1. 布局类组件:职责分离的组合

  • Flex 容器与子元素
    Flex 组件(父)通过 flexDirectionjustifyContent 等属性控制整体布局,子元素(如 divButtonCard 等)无需特殊定义,直接嵌套即可。

    • 逻辑:父组件负责布局规则,子组件仅需遵循父组件的布局约束,两者通过嵌套组合工作,子组件可独立存在(聚合关系)。
  • Grid 栅格系统
    Row(行)和 Col(列)组件组合实现栅格布局:Row 控制行间距,Col 通过 span 属性控制占比,Col 内部可嵌套任意组件(如 InputImage)。

    • 逻辑:RowCol 是“整体-部分”的组合关系,Col 依赖 Row 生效,但 Col 内的子组件可独立使用(聚合)。

2. 复杂结构组件:多子组件协同

  • Table 表格
    Table(容器)、TableColumn(列定义)、TableHeader(表头)、TableRow(行)等子组件组合而成:

    • Table 负责数据管理、分页等整体逻辑;
    • TableColumn 定义每列的标题、数据字段、渲染方式(如插入 Button 作为操作按钮);
    • 子组件各司其职,通过 props 与父组件通信,无法单独脱离 Table 工作(强组合关系)。
  • Form 表单
    Form(管理整体状态)+ Form.Item(字段容器)+ Input/Select 等输入组件组合:

    • Form.Item 负责字段校验、错误提示,Input 负责用户输入,两者通过 Form 的上下文关联;
    • Input 可独立用于非表单场景(聚合关系),Form.Item 则依赖 Form 容器(强组合)。

3. 容器类组件:轻量聚合

  • Card 卡片
    Card.Header(标题区)、Card.Body(内容区)、Card.Footer(操作区)组成,用户可在内容区插入 TextImageButton 等任意组件:

    • 子组件(如 Button)与 Card 是聚合关系,可脱离 Card 单独使用;
    • Card 仅提供容器样式,不干预子组件逻辑,通过组合实现灵活扩展。
  • Dialog 弹窗
    包含标题、内容区、底部按钮区,底部按钮通常用 Button 组件:

    • Button 是独立组件(可用于表单提交、列表操作等),在 Dialog 中仅作为“部分”存在(聚合)。

二、继承的典型例子(极少且受限)

继承在组件库中使用场景狭窄,通常用于“基础能力复用”或“同类型变种”:

1. 基础组件的统一抽象

组件库底层通常有一个 BaseComponent 父类,封装所有组件的通用能力:

  • classNamestyle 等样式属性的处理;
  • 事件监听(如 onClick)、ref 引用传递等基础逻辑;
  • 所有组件(ButtonInputTable 等)都继承 BaseComponent,避免重复实现通用逻辑。
    • 这里的继承是“is-a”关系:Button 是一种 BaseComponent,复用其基础能力。

2. 同类型组件的变种扩展

  • ButtonLinkButton
    LinkButton(链接样式按钮)继承 Button 的核心逻辑(点击事件、disabled 状态等),仅修改样式(去除背景色、边框,模拟链接效果)。

    • 关系:LinkButtonButton 的一种特殊形态,符合“is-a”,且复用 90%以上逻辑,适合继承。
  • InputSearchInput
    SearchInput 继承 Input 的输入逻辑,新增“搜索图标”和 onSearch 事件,本质是“带搜索功能的输入框”,属于 Input 的变种。

三、总结:从例子看优先级

  • 组合无处不在:几乎所有复杂组件(TableFormCard 等)都通过组合实现,核心是“分拆职责,灵活组装”。
  • 继承仅在底层或变种场景使用:如基础能力复用、同类型组件的微小扩展,且需严格满足“is-a”关系。

这也印证了“组合优先于继承”的原则——组合让组件更灵活、低耦合,而继承容易导致逻辑僵化,仅在极特殊场景下适用。