关于 ui-component-architecture
面向使用共享 UI 包的团队,指导编码 Agent 优先复用现有组件,并将合适的展示组件提取到共享包,同时保持页面清晰。
主要功能
- 01盘点并复用共享 UI 导出
- 02让路由专注于组件组合
- 03划分共享组件边界
- 04使用主题令牌和变体
- 05检查包边界与交互行为
适用场景
- 01为含共享 UI 包的应用构建页面
- 02重构含大量内联标记的页面
- 03添加可复用 UI 基础组件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ darkmatter/ skills --skill ui-component-architecture安装后在 Claude Code 中输入/ui-component-architecture确认已加载。
附带文件reference/agents-md-rule.mdreference/eslint-guardrails.md
详细介绍
UI 组件架构
要纠正的失败模式:Agent 用几百行 <div className="..."> Tailwind 代码拼出一个屏幕,重新实现共享 UI 包中已有的 Button、Card 或 Badge,却从未将真正可复用的部分沉淀回该包。结果是样式重复、视觉语言逐渐不一致,屏幕代码也无人能读懂。
解决方法有两个习惯:先复用,再编写,并且通过将可复用单元提取到共享 UI 包中,让屏幕保持精简——但不要过度提取一次性的布局胶合代码。该包 MUST 是独立的包。它的路径和导入别名由各个代码库自行决定(ADR-0013)。从工作区中查找它们(package.json workspaces、packages/*、供应商目录)。不要臆造一个必需的名称。
选择组件边界时,请应用 codebase-design。如果状态、处理器、私有组件和布局共同说明一项能力,就让它们保持在一起。提取应当隐藏行为或实现有用的复用;仅凭 JSX 长度或第二个调用位置,都不足以说明应该提取。
适用场景
- 在包含共享 UI 包的应用中编写新页面/路由/屏幕。
- 为内联 div 较多的现有屏幕重新设置样式或重构。
- 构建看起来像可复用原语的组件(按钮、卡片、徽章、输入框、对话框、空状态、统计信息、骨架屏、头像)。
- 用户说“构建 X 页面”“添加 Y 视图”“创建 Z 组件”。
不适用场景
- 非 React 代码,或没有可复用共享 UI 包的单包应用。
- 没有独立提取职责的一次性布局包装器。
- 编辑现有组件的内部实现,而不是添加新的界面。
原则
先复用,再编写
编写 JSX 之前,先盘点已有内容,避免重复造轮子:
- 共享 UI 包——阅读它的 barrel 或
package.jsonexports。搜索所需内容。 - 外部注册表——对于共享包中尚不存在的区块,请参阅
shadcn-registry-first技能。流程为:注册表 → 应用屏幕 → 将稳定的部分沉淀到 UI 包中。
不要重新实现已有的 Button、Card、Badge、Input、Dialog 或 EmptyState。
保持屏幕精简
路由应让人容易理解其数据和主要视觉能力。组合现有原语,并提取具有明确接口的组件。让连贯的状态和事件处理与其控制的 UI 保持在一起。只转发屏幕属性和标记的组件只会增加跳转,却没有隐藏复杂性。私有组件可以与其所属组件共用一个文件。
将可复用单元沉淀到 UI 包中
判断哪些内容应移入共享包的关键经验法则:
- **沉淀到共享包:**具有独立接口的可复用展示单元,或自包含的视觉原语——
Button、Card、Badge、Avatar、EmptyState、Stat、Skeleton、Input、Dialog。在第二处复用是值得检查的证据;仅凭视觉相似并不能证明它们是同一个单元。 - **留在应用中:**应用特定的组合(某个特定仪表盘的布局)、一次性胶合代码,以及任何包含数据接线或业务逻辑的内容。
- **不确定时,就留在本地。**单次使用的组件也可能隐藏大量行为,但应用特定的属性应归属应用。第二处用法是重新审视边界的提示,而不是把应用状态移入共享包的理由。
使用令牌和变体设置样式,不要散落使用魔法值
- 使用主题令牌(CSS 变量、Tailwind 主题),不要在
className中散布原始十六进制字面值。 - 对于有变体的组件,使用
cn+ 变体映射(或cva),而不是堆积条件类名字符串。 - 将可复用视觉单元的样式集中管理;偶发性的布局样式留在本地。
工作流
1. 清点共享 UI 包
查找该包(工作区列表、packages/*、vendor 目录)。列出它已有的导出项。记下要复用的基础组件,避免重复构建。
2. 将屏幕勾勒为组合结构
在编写细节之前,先将页面列为命名组件树。为每个节点标注:UI 包中已有 / 应用专属组合 / 新建但看起来可复用。这样就能确定要导入什么、内联什么,以及提取什么。
3. 精简地构建屏幕
组合现有基础组件,并将每项交互放在一个易于理解的位置。如果文件超过配置的限制,就按有意义的职责拆分,或记录一个范围明确的限制上调;文件高度本身并不能决定是否选择某个组件。
4. 提取可复用部分
对于每个符合晋升启发式的部分:
- 将其放入 UI 包中,与同类组件放在一起。
- 从 barrel 或
package.jsonexports导出。 - 保持属性通用——不要让应用专属类型、路由、状态存储或 API 客户端泄漏到 UI 基础组件中。共享 UI 组件应保持无状态且只负责呈现;通过属性传入数据。
- 使用
cn/cva将变体放在一起。
5. 验证
- 屏幕从 UI 包导入基础组件,而不是重新定义它们。
- UI 包中的组件均不包含应用专属导入(
@/...、应用路由器、状态存储、API 客户端)。 - 类型检查和构建均通过,使用仓库提供的命令。
- 交互通过组件的公共接口完成;测试应观察用户行为,而不是组件的私有 hook 或辅助函数调用。
哪些内容应晋升,哪些应保留
| 保留在应用中 | 晋升到 UI 包中 |
|---|---|
/dashboard 页面布局 | StatCard、Badge、EmptyState |
| 特定路由的数据获取和接线 | Button、Input、Dialog、Skeleton |
| 仅在一个营销页面使用一次的 hero | 在 3 个以上页面复用的 hero 变体 |
| 业务逻辑、功能开关、应用状态 | 接收属性的呈现型基础组件 |
避免过度提取
过度提取是相反的错误,代价同样高昂。过于急切地提取有以下迹象:
- UI 包组件的属性名称与某个特定屏幕绑定。
- 组件只是转发很长的属性列表,却没有封装交互或呈现决策。
- UI 基础组件导入应用的状态存储、路由器或 API 客户端。
解决办法:将应用专属行为保留在职责清晰的应用组件中。只有在共享基础组件具备实用的呈现接口时才进行提取。即使本地辅助函数和私有组件只用一次,也应将它们放在附近。
参考资料
reference/agents-md-rule.md— 一条简短、可直接粘贴到使用方仓库AGENTS.md中的常驻规则。这是杠杆效益最高的做法:技能按需加载,在“每次 UI 编辑”时可能不会触发;而AGENTS.md规则始终处于上下文中。将其添加到每个包含共享 UI 包的仓库,并填写该仓库实际的包路径和导入别名。reference/eslint-guardrails.md— 通过机械方式强制执行边界的 lint 规则(禁止在应用 JSX 中使用原始十六进制值、限制 JSX 深度,以及最重要的:禁止在 UI 包中导入应用代码的边界规则)。
与其他技能的关系
shadcn-registry-first— 从注册表获取_外部_区块。本技能负责_内部_复用和提取。二者可以配合使用:注册表 → 应用屏幕 → 将稳定部分晋升到 UI 包。darkmatter-design-system— 当应用使用该系统时,负责视觉语言和设计令牌。这些令牌仍存放在 UI 包中;本技能负责包边界。repository-organization— 负责目录和包的组织位置。本技能关注更细一层的组件复用粒度。vercel-react-best-practices— 适用于你编写和组合的组件的性能模式。