用途
Component Reuse First 是一套针对 UI 组件开发的编码规范,用于约束在创建界面组件时的行为:在动手写新组件之前,先穷尽式地检查现有组件是否已经覆盖需求,只有在复用确实不可能时才新建,并且新建时面向下一次复用进行设计。
核心工作流
1. 创建前必须搜索
在编写任何新组件之前,并行执行以下检查:
- Glob 扫描常见组件目录:
**/components/**/*.{tsx,jsx,ts,js,vue,svelte}**/ui/**/*.{tsx,jsx,vue,svelte}**/shared/**/*.{tsx,jsx,vue,svelte}**/design-system/**/*、**/lib/components/**/*
- Grep 检索概念名与同义词:
- 用户要 “Modal” → 检索
Modal|Dialog|Popup|Overlay|Sheet|Drawer - 用户要 “Button” → 检索
Button|Btn|IconButton|CTA - 用户要 “Card” → 检索
Card|Panel|Tile|Box - 用户要 “Input” → 检索
Input|TextField|TextInput|FormField - 用户要 “List” → 检索
List|Table|DataGrid|Items
- 用户要 “Modal” → 检索
- 检查导入:查看相似页面或特性已经在使用什么
- 检查 package.json:识别已存在的设计系统库(shadcn/ui、MUI、Chakra、Mantine、Ant、Radix、HeroUI、NextUI、Tamagui、native-base 等),优先使用它们而不是手写
2. 决策树
搜索之后:
- 存在完全匹配 → 原样复用,不要重复实现。
- 存在接近匹配(90% 以上) → 通过 props/变体扩展现有组件,不要分叉。
- 存在部分匹配 → 将现有组件重构为基础组件加变体,或通过组合实现,避免复制粘贴。
- 没有匹配 → 新建,并遵循下文的可复用默认规则。
在写代码前明确向用户说明决策,例如:“在 src/ui/Button.tsx 找到带 variant prop 的 Button —— 新增 ghost 变体”或“没有匹配的 modal —— 在 src/ui/Modal.tsx 新建 Modal”。
3. 新建时的可复用默认规则
当确实需要新建组件时,为下一个调用者而建,而非只为当前场景:
放置位置
- 共享/通用 →
components/ui/、src/ui/或项目的设计系统路径 - 特性内专用(仅在某特性内有意义)→
features/<feature>/components/ - 绝不把可复用基础组件放进路由/页面文件
API 设计
- 接受
className并转发 ref(React:forwardRef+cn(className)模式) - 将剩余 props 展开到底层元素(
...props/v-bind="$attrs") - 使用变体(如
cva、tv、prop 联合类型)而不是复制组件 - 使用 children/slot 而非硬编码内容
- 使用语义化 prop 名称(
variant、size、tone、intent),而非视觉化名称(bigBlueButton) - 必填与可选:最小化必填接口,其余提供合理默认值
组合优于配置
- 优先使用复合组件(
Card.Header、Card.Body)而非巨型 prop 集合 - slot/children 模式优于大量
*Contentprop
样式
- 沿用项目既有方案(Tailwind、CSS modules、styled-components、vanilla-extract),不要引入新的样式系统
- 使用设计令牌/主题值,不要硬编码颜色或间距
类型安全(TS 项目)
- 扩展原生元素 props:
ButtonHTMLAttributes<HTMLButtonElement> - 行为不同时使用可辨识联合定义变体(如
as="link" | "button")
无障碍
- 默认使用正确的语义元素(button 对 div)
- ARIA 属性通过 props 传递
- 键盘交互在基础组件层处理
4. 创建之后
- 若项目使用 barrel 文件,从目录的
index.ts导出 - 若项目有 Storybook/文档,补充进去
- 如果搜索时发现多个现有组件可被新组件替代,告知用户(不要未经要求就重构)
需要拒绝的反模式
- ❌ 在已有
Button旁边创建Button2、NewButton、CustomButton - ❌ 在页面内联定义明显可复用的组件
- ❌ 复制粘贴组件只为改两行,而不是加一个 prop
- ❌ 硬编码文案/颜色/尺寸导致无法复用
- ❌ 已有 UI 库时再引入第二套设计系统
- ❌ 以“组件很简单”为理由跳过搜索步骤
快速判断法则
- 如果你打下
function Button(而仓库里已有Button→ 停下,复用。 - 如果新组件没有任何 props → 它大概率不可复用,询问是否应接受内容/变体。
- 如果你即将写下 20 分钟前在另一个文件见过的相同 JSX → 抽取出来。
示例交互
用户: “给删除操作加一个确认弹窗。”
预期: 检索 Modal|Dialog|Confirm,在 src/ui/ConfirmDialog.tsx 找到 ConfirmDialog → 复用,传入 title、onConfirm、destructive。
用户: “创建一个定价卡片组件。”
预期: 检索 Card,在 src/ui/Card.tsx 找到 Card 基础组件 → 用 Card + Card.Header + Card.Body 组合出 PricingCard,放在 features/pricing/components/,不要重新造卡片框架。
用户: “我需要一个带加载旋转器的按钮。”
预期: 检索 Button,找到的按钮没有 loading prop → 给现有 Button 加 loading?: boolean,不要创建 LoadingButton。
激活触发条件
当对话涉及以下内容时该技能应激活:
- 创建、构建、搭建或添加 UI 组件
- 关键词:component、button、modal、dialog、card、form、input、table、list、dropdown、menu、sidebar、header、footer、layout、page、view、screen、widget
- 框架:React、Vue、Svelte、Angular、Solid、SwiftUI、Jetpack Compose、Flutter
- 短语:“make a”、“build a”、“I need a”、“create a”、“add a” + UI 名词