Monorepo UI 组件包设计:从设计系统到可维护组件库
该文档阐述 Monorepo UI 组件包设计,分为设计基础、原子、复合组件、UI 工具四层。遵循零业务耦合、主题化等原则,依托 Design Tokens 构建视觉体系,规范表单、浮层、图标实现,配套构建、Storybook 文档、测试,厘清通用 UI 与业务组件边界。
UI 组件包不是简单的组件集合,而是设计规范、主题系统、交互模式、组件 API 与文档工具链的组合。
1. UI 组件包的定位
一个成熟的 UI 组件包需要同时解决两个问题:
- 视觉一致性:不同应用使用相同的设计语言;
- 交互一致性:相同场景具有相近的行为、状态和反馈。
原方案将 UI 组件包定位为“设计系统 + 组件库 + UI 工具链”,而不是简单的组件堆积。fileciteturn1file1L43-L63
2. UI 组件包的四层结构
推荐从底到上划分为四层:
Design Foundation
↓
Atomic Components
↓
Composite Components
↓
UI Utilities / Hooks| 层级 | 内容 | 稳定性 |
|---|---|---|
| 设计基础层 | Design Tokens、主题、全局样式、响应式 | 极高 |
| 原子组件层 | Button、Input、Image 等 | 高 |
| 通用复合层 | Select、Dialog、Toast、Tabs 等 | 中高 |
| UI 工具层 | useDialog、useToast 等 | 中 |
原方案采用相同的四层划分。fileciteturn1file1L52-L56
3. 核心设计原则
3.1 零业务耦合
组件库不应该直接调用业务 API,也不应该知道业务领域实体。
不推荐:
<UserPaymentButton userId={id} />如果按钮内部直接发起支付请求,它已经不再是通用 UI 组件。
推荐:
<Button loading={loading} onClick={handlePay}>
Pay
</Button>业务行为由上层注入,组件负责视觉和交互。
3.2 可组合
组件应该像积木一样被组合,而不是把所有业务场景预先做成一个巨大组件。
3.3 主题化
颜色、字号、间距、圆角、阴影等视觉属性应该由 Design Tokens 控制。
3.4 类型完备
所有 Props、事件、Ref 和插槽都应该具有明确的 TypeScript 类型。
这些原则与原方案中的零业务耦合、可组合性、主题化驱动和类型完备保持一致。fileciteturn1file1L64-L70
4. Design Tokens:组件库的视觉内核
Design Tokens 是组件库长期维护的基础。
常见 Token 包括:
颜色
- Brand;
- Success;
- Warning;
- Error;
- Neutral;
- Text;
- Background。
间距
例如:
4 / 8 / 12 / 16 / 24 / 32字体
例如:
12 / 14 / 16 / 20 / 24其他视觉参数
- Border Radius;
- Shadow;
- Border;
- Motion Duration;
- Easing。
原方案建议将 Design Tokens 沉淀到共享 Tailwind Preset,再由组件库复用,避免多个包重复定义。fileciteturn1file1L43-L77
5. ThemeProvider 与 CSS Variables
推荐由主题 Provider 提供主题上下文:
<ThemeProvider theme="dark">
<Button>Dark Theme</Button>
</ThemeProvider>视觉 Token 最终可以映射为 CSS Variables:
:root {
--color-primary: ...;
--color-background: ...;
--radius-md: ...;
}这样可以支持:
- Light / Dark;
- 运行时切换;
- 局部主题覆盖;
- 多品牌主题。
6. 原子组件
原子组件数量不需要无限增加,但必须稳定。
Button
应该覆盖:
- Primary / Secondary;
- Loading;
- Disabled;
- Icon Button;
- 点击反馈。
Input
应该覆盖:
- Text;
- Number;
- Password;
- Search;
- Textarea;
- Prefix / Suffix;
- Error;
- Disabled;
- Controlled / Uncontrolled。
Image
除了显示图片,还应处理:
- Loading;
- Placeholder;
- Error fallback;
- Lazy loading;
srcset;object-fit;alt;- CDN 参数。
原方案将 Button、Icon、Typography、Input、Image 作为高频原子组件,并强调状态一致性和可访问性。fileciteturn0file2L89-L108
7. 导航与流程组件
常见组件包括:
- Breadcrumb;
- Tabs;
- Dropdown;
- Pagination;
- Steps;
- Menu。
这些组件的重点不是“能显示出来”,而是统一页面流转、状态、键盘操作和响应式行为。
8. 表单体系
表单通常是企业级 UI 组件库中复用率最高的一部分。
推荐分层:
Input
↓
Select / DatePicker / TimePicker
↓
Form
↓
ValidationForm 应该负责:
- 布局;
- 字段注册;
- 取值与赋值;
- 重置;
- 提交;
- 校验时机;
- 字段联动。
但业务校验规则不应该写死在组件库中。原方案明确要求 Form 只负责通用表单控制,业务规则由上层注入。fileciteturn0file2L124-L153
9. Feedback 与 Overlay
反馈组件负责统一操作结果和交互反馈:
- Message;
- Notification;
- Tooltip;
- Popover;
- Popconfirm;
- Modal;
- Drawer。
这类组件需要统一处理:
- z-index;
- Portal;
- ESC;
- Focus;
- 键盘操作;
- Loading;
- 动画;
- 可访问性。
原方案特别强调浮层 z-index 的统一管理以及 ESC 关闭和无障碍支持。fileciteturn0file2L154-L169
10. UI Hooks
UI Hooks 与基础 Hooks 的边界应该明确。
适合组件包:
useModal
useToast
useDrag
useTextOverflow适合基础工具包:
useDebounce
useThrottle
useInterval
useLocalStorage判断标准可以概括为:
只要 Hook 的语义依赖 UI 行为,就应该靠近 UI 组件;如果只是纯逻辑能力,则应该放在基础 Hooks。
原方案也采用这一边界。fileciteturn0file2L170-L179
11. 图标体系
图标建议独立为 @org/icons:
- SVG 组件化;
- 支持按需加载;
- Tree Shaking;
- 统一尺寸;
- 单色 / 多色;
- 自定义颜色。
这样可以避免图标数量增长后把整个组件包的构建和发布周期绑定在一起。原方案也建议图标库独立成包,并与组件库配套。fileciteturn0file2L180-L196
12. 构建策略
推荐:
- ESM;
- CJS;
- DTS;
- sourcemap;
sideEffects: false;- 按组件导入。
例如:
{
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs",
"types": "./dist/index.d.ts"
}
},
"sideEffects": false
}如果采用 Tailwind 体系,可以输出独立 CSS;如果采用 CSS-in-JS,则需要关注运行时开销和按需注入。原方案将两种样式策略都列为可选方案。fileciteturn0file2L205-L222
13. Storybook:让组件成为可观察资产
每个组件建议拥有对应 Story:
Button
├── Default
├── Loading
├── Disabled
├── WithIcon
└── LongTextStory 不只是 Demo,还可以成为:
- API 使用示例;
- 视觉回归入口;
- 交互测试入口;
- 设计评审入口;
- 文档入口。
原方案将 Storybook 文档站作为组件库工程化的重要组成部分,每个组件对应 stories、API 说明和可交互 Playground。fileciteturn0file2L223-L227
14. 组件 API 设计
组件 API 建议遵循:
- Props 语义稳定;
- 受控与非受控行为明确;
- 默认行为可预测;
- 状态类型完整;
- Ref 行为明确;
- 不把业务字段写入通用组件;
- 避免过多 boolean Props。
例如:
<Button
variant="primary"
size="md"
loading={loading}
disabled={disabled}
onClick={handleClick}
/>而不是把多个业务行为塞进:
<Button isPayButton isVipButton isDepositButton />15. 标准目录结构
packages/components/
├── src/
│ ├── index.ts
│ ├── theme/
│ ├── button/
│ ├── input/
│ ├── form/
│ ├── modal/
│ ├── hooks/
│ └── __tests__/
├── stories/
├── package.json
├── tsconfig.json
├── tsdown.config.ts
└── README.md16. 版本策略
UI 组件的破坏性变更不仅可能影响 TypeScript API,还可能影响视觉和交互,因此应该同时关注:
- Props 兼容性;
- CSS class / Token;
- DOM 结构;
- 键盘行为;
- 默认尺寸;
- 默认间距;
- 动画行为。
原方案特别指出 UI 组件升级影响包括视觉和交互,维护重点是 API 与视觉行为双向稳定。fileciteturn1file1L57-L63
17. 测试体系
建议至少包含:
- 单元测试;
- 交互测试;
- Accessibility 测试;
- Storybook 场景;
- 视觉回归;
- 构建产物测试。
对于 Modal、Dropdown、Tooltip、Form 等复杂组件,应重点测试焦点、键盘、Portal、异步状态和边界尺寸。
18. 组件何时应该进入业务域
一个非常重要的边界是:
通用 Button
↓
通用 Dialog
↓
业务 UserDialog当组件开始出现以下特征时,就应该考虑上移到业务域包:
- 包含业务字段;
- 调用业务 API;
- 包含领域状态;
- 只能被某个业务域使用;
- UI 行为与具体业务流程强绑定。
这样可以避免 UI 包最终变成“业务大杂烩”。
19. 总结
UI 组件库真正的核心资产不是组件数量,而是:
稳定的设计 Token、清晰的组件边界、可组合 API、统一的交互行为,以及完整的文档和测试体系。
组件越底层,越应该保持通用;业务越具体,越应该留在业务域包。
Monorepo 内部私有包设计:让内部能力保持灵活,又不污染公共依赖体系
介绍 monorepo 中`private:true`内部私有包,依托 workspace:* 实现源码内部复用、不对外发布。划分工程脚本、测试辅助等六类包,区分开发态与运行时风险,明确禁止公开包依赖私有包,配套命名、质量分级与完整生命周期管理。
Monorepo 业务域包设计:用领域边界承载可复用业务能力
文档讲解 Monorepo 业务域包,位于应用层之下,基于 DDD 限界上下文拆分领域。分为领域模型、服务、状态等七层结构,实现业务逻辑与 UI 框架解耦,规范依赖与跨域交互方案,定义目录、对外导出 API、测试策略,避免业务逻辑散落在应用内。