3、组件库架构设计:目录结构规划、组件分类策略、按需加载设计、主题系统设计思路
好,咱们进入第三章节。说实话,很多同学做组件库,上来就写代码。结果写到一半,目录乱成一锅粥,组件之间互相依赖,改一个按钮要重新打包整个库。嗯,我早期踩过这个坑,后来才明白——架构设计才是组件库的骨架。骨架歪了,后面怎么长都是歪的。
这一章,我会把四个核心设计点掰开揉碎讲清楚:目录怎么规划、组件怎么分类、怎么实现按需加载、主题系统怎么设计。都是我在真实项目中沉淀下来的经验。
3.1 目录结构规划:别让文件找文件
我个人习惯,组件库的根目录一定要清晰。你想想看,一个库可能有几十上百个组件,如果目录结构混乱,新同事来了根本不知道往哪放代码。
我推荐这种分层结构:
my-component-library/
├── packages/ # 核心包
│ ├── components/ # 组件源码
│ │ ├── button/
│ │ │ ├── index.tsx
│ │ │ ├── style.css
│ │ │ └── types.ts
│ │ ├── input/
│ │ └── ...
│ ├── theme/ # 主题系统
│ │ ├── tokens.ts
│ │ └── mixins.ts
│ └── utils/ # 工具函数
├── docs/ # 文档
├── scripts/ # 构建脚本
├── test/ # 测试
└── package.json
这里有个关键点:每个组件独立一个文件夹。为什么?因为后续做按需加载时,每个组件就是一个独立的入口。我在项目中遇到过,有人把所有组件塞到一个文件里,结果按需加载根本没法做,只能全量引入。
核心原则:组件之间不要互相引用。如果两个组件有公共逻辑,抽到 utils 里。否则,你改一个组件,会牵连一片。
3.2 组件分类策略:分而治之
组件不是随便堆的。我一般把它们分成三类:
| 分类 | 说明 | 例子 |
|---|---|---|
| 基础组件 | 原子级,不依赖业务 | Button、Input、Icon |
| 复合组件 | 由基础组件组合而成 | Form、Table、Modal |
| 业务组件 | 带业务逻辑,可复用 | UserPicker、ChartCard |
为什么要这么分?说白了,基础组件要稳定,业务组件要灵活。我曾经在一个项目里,把业务逻辑直接写进了 Button 组件。结果换了个项目,Button 就废了,还得重写。嗯,从那以后,我坚持基础组件只做 UI,不做业务。
小技巧:基础组件的 props 尽量少,10 个以内最好。业务组件可以多一些,但要有默认值。
3.3 按需加载设计:别让用户全量引入
这是个大问题。很多组件库,用户只用了 Button 和 Input,结果打包出来几百 KB。为什么?因为全量引入了。
按需加载的核心思路是:每个组件独立打包,用户只引入需要的。我常用的方案有两种:
- ES Module 方式:每个组件导出独立的模块,配合 tree-shaking。
- babel-plugin-import:自动转换 import 语句,按需加载。
举个例子,用户这样写:
import { Button } from 'my-lib';
经过 babel 插件转换后,变成:
import Button from 'my-lib/es/button';
import 'my-lib/es/button/style.css';
你看,只加载了 Button 的代码和样式。其他组件根本没进包。
注意:按需加载要求每个组件必须有独立的样式文件。如果所有样式写在一个文件里,按需加载就失效了。我见过有人把样式全写在 index.css 里,结果按需加载后样式全乱了。
另外,组件之间不要有循环依赖。比如 A 组件引用了 B,B 又引用了 A。这种在按需加载时会导致死循环。我曾经排查过这种问题,花了两天时间才找到原因——两个工具函数互相调用了。
3.4 主题系统设计思路:让用户自定义
主题系统,说白了就是让用户能换皮肤。我常用的方案是 CSS 变量 + Token 系统。
先定义 Token:
// tokens.ts
export const lightTokens = {
colorPrimary: '#1890ff',
colorBg: '#ffffff',
fontSize: '14px',
borderRadius: '4px',
};
export const darkTokens = {
colorPrimary: '#177ddc',
colorBg: '#141414',
fontSize: '14px',
borderRadius: '4px',
};
然后生成 CSS 变量:
// 生成 CSS 变量
:root {
--color-primary: #1890ff;
--color-bg: #ffffff;
--font-size: 14px;
--border-radius: 4px;
}
[data-theme="dark"] {
--color-primary: #177ddc;
--color-bg: #141414;
}
组件里直接用变量:
.button {
background-color: var(--color-primary);
font-size: var(--font-size);
border-radius: var(--border-radius);
}
这样用户只需要切换 data-theme 属性,整个组件库的样式就变了。我在项目中遇到过,用户想自定义主题,但我们的组件库用了硬编码颜色。结果用户只能自己覆盖样式,维护起来特别痛苦。后来我改成 CSS 变量方案,用户只需要改几个 Token 值就行。
核心思路:Token 是设计语言,CSS 变量是技术实现。两者分离,用户改 Token 不影响组件逻辑。
另外,主题系统要支持嵌套。比如某个页面用暗色主题,其他页面用亮色主题。CSS 变量天然支持作用域,只要在对应 DOM 节点上设置 data-theme 就行。
3.5 总结一下
这一章的内容,说白了就是四个字:先想后做。目录结构、组件分类、按需加载、主题系统,这些都是在写第一行代码前就要想清楚的。我见过太多项目,前期图省事,后期重构到崩溃。
嗯,下一章我们会进入具体的组件开发环节。到时候我会手把手带你写一个 Button 组件,从设计到实现,再到测试。敬请期待。
课后思考:如果你的组件库要支持多个主题(比如 10 个以上),Token 系统该怎么设计?欢迎在评论区讨论。