组件「功能不够用」?先查命名是否混用了两个概念
做设计系统那段时间,同事拿着一张截图过来说:"这个 Badge 组件功能太少了,能不能支持角标效果?"——图上是 iOS 风格的红色计数圆点,挂在图标右上角。
我第一反应是加 prop,写一下 position="corner" 之类的 API。加了一半才发现哪里不对劲:这个 Badge 根本不是做角标用的,它就是一个内联文字 pill。两件事被塞进同一个名字里了。
现象
用户给出一张参考图:图标右上角有红色"3",头像右下角有绿色"✓"——典型的 iOS 覆盖式角标。
他们打开组件文档,只看到一堆颜色各异的文字标签(<Badge variant="success">已完成</Badge> 这类),根本做不出那个效果,于是断定「Badge 不够用」。
代码里还能找到更直接的证据:三处业务代码手写了 absolute -top-1 -right-1 来伪造角标,说明这个缺口早就存在了,只是一直靠 hack 绕过去。
根因
这个设计系统里有三个 pill 型组件:Badge、Tag、Chip。三者外观高度相似,各自的注释说:
tag.tsx:"Tag = 状态标签(区别于 Badge 计数 / Chip 令牌)"chip.tsxmanifest:"区别 Badge 计数"
两处注释都明确说 Badge 应该是计数 / 角标,但实现里 Badge 却是个内联 pill。名字的语义已经对了,实现漂移了。
根源在于设计系统早期照搬了 shadcn/ui 的 Badge(shadcn 的 Badge 是文字 pill,连角标功能都没有),而没有注意到 Ant Design / MUI 世界里 Badge 特指「覆盖式计数徽标」这个完全不同的概念。
当 Badge 同时指「inline 文字 pill」和「overlay 角标计数器」时,两种需求都无法被第一类 API 满足。用户会抱怨「功能少」,开发者会不断往组件里加 prop,最终变成一个 props 爆炸的怪物——而根本问题是概念没有分清。
成熟库的参考分界:
- Badge(角标语义):Ant Design
Badge、MUIBadge— 包裹子节点,在角落渲染计数/点/自定义内容,带count、dot、placement、offset等 prop。 - pill 语义:Ant Design
Tag、MUIChip、shadcnBadge(命名异类)— 内联展示状态文字,不包裹子节点。
正解
第一步:精确命名两个概念。
- 当前
Badge实现 = 内联 pill(应归入Tag/Chip) - 用户需要的 = 覆盖式角标(才是「真 Badge」)
第二步:收回名字,迁移漂移的用法。
把 Badge 重建为覆盖式角标组件:
// Badge — overlay count/dot/content
interface BadgeProps {
count?: number;
max?: number; // 超出显示 max+
dot?: boolean; // 纯红点,不显示数字
content?: ReactNode; // 自定义内容(图标/文字)
placement?: 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
offset?: [number, number];
ring?: boolean; // 白色描边,分离宿主背景
children: ReactNode; // 被包裹的目标元素
}
// 用法
<Badge count={3}>
<Icon name="bell" />
</Badge>
<Badge dot color="green">
<Avatar src={avatarUrl} />
</Badge>第三步:把原来 pill 用途全部迁移到 Tag。
Tag 本就是 pill 的超集(颜色、border、closable、icon),迁移是纯视觉等价替换:
// Before(漂移的用法)
<Badge variant="success">已完成</Badge>
// After(语义正确)
<Tag color="success">已完成</Tag>第四步:消灭手写角标 hack。
// Before — 业务代码手写 absolute 伪造角标
<div className="relative">
<Icon name="message" />
<span className="absolute -top-1 -right-1 ...">3</span>
</div>
// After — 一行搞定
<Badge count={3}><Icon name="message" /></Badge>当组件「感觉不够用」时,先读同一组件库里兄弟组件的 doc-comment / manifest description。如果注释已经写了「区别于 Badge 计数」,说明库的作者早就规划好了分工,漂移的是实现,不是名字。读注释 → 确认意图 → 迁移漂移用法,比直接堆 prop 代价小得多。
一句话外卖
组件「功能不够用」时,先问自己:这个名字是否同时承担了两个不同 UI 类别?成熟库(Ant Design / MUI)的命名拆分方式是最快的分类参照。