如何让组件库兼顾灵活性和一致性

3 阅读4分钟

如何让组件库兼顾灵活性和一致性

做B端前端最痛苦的事:组件库管太死,业务方抱怨"这破东西根本满足不了需求";管不住,设计稿和代码各走各的,一致性全面崩塌。

这不是二选一的问题,是你架构没搭好。

问题本质:为什么这是个伪命题

先搞清楚两件事。

SaaS多租户需要灵活性。租户A要蓝色主题,租户B要绿色主题,你不能改代码,只能靠配置。

企业级系统需要一致性。100个页面必须有统一的交互逻辑,新人接手不会懵。

这两个需求同时存在于B端产品里。你没办法通过"规范约束"解决结构性问题。

一致性失控的信号

你的组件库已经出问题了,如果你看到这些:

  • Button组件有12种颜色变体
  • Modal有4种不同的关闭方式
  • Select的placeholder有"请选择"、"请输入"、"请选择内容"三种文案

每个变体都有人"合理地"创建出来。然后维护成本爆炸。

灵活性失控的信号

反过来的问题:

  • 3个业务方fork了你的组件库
  • 主库半年没人更新,因为没人敢动
  • 每次升级都是大型Battle现场

当业务方开始fork,说明你的组件库跟他们没关系了。

三层架构:灵活性与一致性的解法

我的方案是分层设计

层级职责业务方能改吗
基础层纯UI原子,零业务逻辑不能
组合层基于基础层组合业务逻辑可以,但有约束
配置层运行时配置,零代码定制完全开放

基础层:锁定,不妥协

Button、Input、Icon、Badge这些原子组件是基础设施,不业务化。

// ✅ 对的:基础组件只有属性,没有业务假设
<Button type="primary" size="large" />

// ❌ 错的:业务变体混入基础组件
<Button type="primary" intent="danger" meaning="delete" />

intent、meaning 这些业务概念不应该出现在 Button 的props里。

组合层:给空间,但要守规矩

组合层基于基础层组合出业务组件。这里可以定制,但要遵守协议。

// 业务组件:SearchFilter
// 业务方可以改,但不能破坏接口契约

interface SearchFilterProps {
  // 这些是契约,业务方必须实现
  onSearch: (values: Record<string, any>) => void;
  onReset: () => void;

  // 扩展点:业务方可以注入自定义字段
  extraFilters?: React.ReactNode;

  // 锁定:内部状态业务方碰不到
  // privateState?: any;  // 不暴露
}

组合层的关键是明确什么能改,什么不能改

配置层:完全开放,零门槛

主题配置、Feature Flags、业务参数都放在配置层。

// theme-config.json
{
  "primaryColor": "#1890ff",
  "enabledModules": ["export", "batchDelete"],
  "dateFormat": "YYYY-MM-DD"
}

业务方改配置就行,不需要改代码。

4个关键设计模式

1. Design Token:用变量锁定视觉一致性

/* tokens.css */
:root {
  --color-primary: #1890ff;
  --color-danger: #ff4d4f;
  --space-md: 16px;
  --radius-md: 6px;
}
// 组件里用token,不直接写死颜色
const Button = ({ type }) => (
  <button
    className={type}
    style={{ backgroundColor: 'var(--color-primary)' }}
  >
    {children}
  </button>
);

改主题?换一个token文件就行。组件代码不用动。

2. Compound Components:让内部组合更灵活

// 反模式:所有props堆在一起
<Select
  placeholder="请选择"
  options={[]}
  onChange={handleChange}
  showSearch
  allowClear
  multiple
  tags
  // ...20个props
/>

// 正模式:组合模式
<Select placeholder="请选择">
  <Select.Option value="1">选项1</Select.Option>
  <Select.Option value="2">选项2</Select.Option>
  <Select.Search />
  <Select.Creatable /> {/* 按需引入 */}
</Select>

Compound Components 让使用者从"记props"变成"组合能力"。灵活性来自组合,一致性来自约束。

3. Props Schema 收敛:统一接口

不一致的接口是维护噩梦:

// 三种写法,折磨人
onChange: (value) => {}           // Ant Design
handleSelect: (value) => {}      // 某业务组件
onItemClick: (item, index) => {} // 又一个组件

我的规则

  • 选中/变化事件:统一用 onChange: (value, item?) => void
  • 确认事件:统一用 onConfirm: () => void
  • 取消事件:统一用 onCancel: () => void

事件命名收敛后,业务方根本不需要查文档。

4. Slot 机制:扩展点设计

interface CardProps {
  title: string;
  extra?: React.ReactNode;  // 标题栏右侧扩展
  footer?: React.ReactNode; // 底部扩展
  children?: React.ReactNode;
}

// 使用方
<Card title="用户列表" extra={<Button>导出</Button>}>
  <UserTable />
</Card>

扩展点让组件在锁定结构的同时,支持任意位置的内容定制。

治理规范:光靠设计不够,还得管

组件准入门槛

新组件入库必须满足:

  1. 至少3个业务场景在用
  2. 有完整的设计稿
  3. 通过一致性评审

防止"拍脑袋"组件入库,半年后没人维护。

变更评估清单

每次改动回答3个问题:

  1. 影响范围:这次改动破坏几个现有组件?
  2. 迁移成本:业务方需要改多少代码?
  3. 为什么不能通过配置解决?

如果能通过配置解决,就不要改组件。

自动化检查

// package.json scripts
{
  "lint:components": "eslint src/components --rule 'no-unstable-nested-components: error'"
}

一致性检查进CI。不合规的代码根本merge不进去。

最后

好的组件库不是告诉业务方"你按我说的做",而是"你想做的,我支持你做"。

一致性不是约束出来的,是架构设计出来的。


相关推荐