在大型 Vue 项目中,当多个业务线、多个页面开始复用同一套 UI 元素时,散落在各个模块里的组件会成为维护的噩梦——改一个按钮样式要改几十个文件,A 团队写的表格 B 团队不知道,重复造轮子比比皆是。此时就需要将组件体系化沉淀为公共组件库和业务组件库,形成项目的“基建资产”。
一、两层组件库的定位与划分
并不是所有复用组件都该扔进同一个筐。合理分层是落地的第一步:
1. 公共组件库(基础 UI 层)
定位:与业务无关、可在项目间甚至公司内复用的通用 UI 组件。
典型内容:
- 基础元素:Button、Input、Modal、Table、Select、DatePicker 等
- 布局容器:Layout、Grid、Card、Tabs、Collapse
- 交互反馈:Toast、Loading、Popover、Tooltip
- 工具类组件:Icon、Avatar、Divider、BackTop
这些组件追求高复用、低耦合,不包含任何业务逻辑、业务文案或业务接口调用。它们对外暴露清晰的 Props、Events 和 Slots,像标准件一样被业务方使用。
2. 业务组件库(领域抽象层)
定位:封装了特定业务领域内重复出现的 UI 与交互模式,供同一业务域下的多个页面或不同业务线复用。
典型内容:
- 用户选择器(带公司组织架构数据)
- 商品卡片(含价格格式化、库存状态标签)
- 订单状态流转步骤条
- 权限按钮(自动根据权限控制显隐)
- 数据导出面板(内置导出格式选择、服务端交互)
业务组件允许内部请求接口、持有业务状态、调用业务工具函数,但对外仍通过 Props 和 Emits 保持接口的干净。例如,<UserSelector v-model="selectedUsers" :max="10" />,外部只关心选中了谁、最多选几个,内部如何拉取数据、搜索、分页外部无需关心。
划分原则:如果一个组件不引用任何业务域名下的常量、接口或类型,它就是公共组件;一旦开始 import 业务接口或常量,就应划入业务组件库。
二、组件库的技术选型与工程搭建
组件库不是一个独立的“项目”,而是一个独立仓库 + 按需发包 + 业务方安装使用的工程体系。
常用技术栈:
- 包管理:pnpm workspace 或 Turborepo 组织 monorepo(将公共库、业务库、应用放在同一个仓库下管理,方便联调)
- 构建工具:Vite library mode,快速打包 ESM/CommonJS/UMD 格式
- 开发环境:利用 VitePress 或 Storybook 搭建组件文档与示例游乐场
- 类型支持:TypeScript 严格模式,对外暴露完整的 .d.ts 声明
- 测试:Vitest + Vue Test Utils 覆盖核心组件
关键配置要点:
- 在
package.json中正确声明main、module、types入口,支持 Tree Shaking - 将
vue和pinia等宿主应用的依赖标记为externals,避免重复打包 - 组件按需引入可通过单独导出(如
import Button from 'ui-lib/button')或使用unplugin-vue-components自动按需加载 - 样式隔离:公共组件库可以使用 CSS 变量暴露主题定制,业务方覆盖变量即可换肤
三、组件库的开发规范与质量保障
有库不代表好用,乱糟糟的 API 会逼着业务方继续自己写。必须建立一套铁律:
API 设计规范:
- Props 一致性:同类属性命名统一,如所有可关闭弹窗都用
visible+@close,不要东一个show西一个open - v-model 标准:尽量遵循 Vue 官方推荐,对于双向绑定数据,用
modelValue,也可扩展多个 v-model(如v-model:visible) - 默认值与必填:所有 Prop 都应有合理的默认值,必要的校验加上类型与自定义校验函数
- 插槽命名语义化:如表格组件的
columns配置可以配合具名插槽header-{field}、body-{field},而不是抽象的数字序号
文档与示例:
- 每个组件至少包含:功能描述、Props 表格(类型、默认值、说明)、Events 表格、Slots 表格、3 个以上的使用示例(基础、进阶、边界情况)
- 文档即开发环境,支持实时编辑代码并预览效果(Storybook 或 VitePress 内嵌 Playground)
变更与版本管理:
- 严格遵守语义化版本(SemVer):公共组件库的 breaking change 需要升主版本号,业务方升级时会有意识地检查适配
- 每个版本的 CHANGELOG 清晰列出新增、修复、废弃项
- 发布前跑通全量单元测试 + 构建校验,最好接入 CI 自动发布
四、业务组件库的沉淀机制
业务组件不是设计出来的,是重构时抽出来的。强制“预先规划一个完美业务组件库”往往会变成空壳。更务实的沉淀路径是:
1. 发现重复
当同一个业务逻辑在三个以上页面出现时(比如文件上传前的格式校验 + 进度条 + 错误提示),团队成员应提出抽取候选。
2. 抽象复用单元
不是把整块代码原封不动搬走,而是识别哪些是稳定不变的模式(上传流程),哪些是变化点(允许的文件类型、最大体积),将变化点定义为 Props 或插槽。
3. 评审入库
业务组件代码由编写者提交到业务组件库仓库,发起 MR/PR,由至少一位其他开发者 Review 通过后合入。评审关注点:
- 接口设计是否合理,命名是否符合规范
- 是否残留了硬编码的业务文案或接口地址
- 是否考虑了边界状态(空数据、加载中、报错)
4. 持续演进
业务组件库需要随着业务发展不断迭代。鼓励业务方反馈需求或直接贡献代码,形成“使用—反馈—改进”的循环。
五、落地中的真实经验
不要过早抽象
需求还没想清楚时就建组件库,八成会变成过度设计。宁可先让代码在业务模块里“重复”一两次,等模式稳定后再抽取。第三次出现同样逻辑时,毫不犹豫抽组件。
公共与业务的隔离红线
公共组件库里绝对不能出现业务接口调用,更不能依赖 Pinia 的某个业务 Store。一旦公共组件库依赖了业务模块,升级和复用就会炸——维护者要同时关注多个业务线的兼容性,根本不敢动代码。如果某个基础组件需要从服务端获取选项数据(如城市选择器),可以通过 options Prop 传入数据,获取数据的逻辑交给业务层处理。
样式覆盖策略
业务方使用公共组件时,难免需要进行细微的样式调整。提供两类标准“修改通道”:
- Props 控制:如 Button 提供
type、size、round等预设变化 - CSS 变量:如
--btn-primary-bg: #1890ff,业务方覆盖这些变量实现主题定制 - 极少情况:如果以上都不满足,允许使用
:deep()进行局部的样式穿透,但需在代码审查中写明理由,避免滥用导致升级困难。
文档就是门面
一个内部组件库成功与否,很大程度取决于同事愿不愿意打开你的文档网。文档写得像个 API 说明书的缩印就没人看——要给出复制就能跑的代码块,展示常见组合玩法,还要记录容易踩坑的点(如 v-model 在组件里不能用 .sync 了)。活得好的组件库,文档一定是不断被吐槽、不断被打磨的状态。
沉淀一套组件库,本质上是把团队中“写得好的人”的产出转化为全团队的效率。一开始可能会觉得花时间,但当某个新需求只需要排列组合几个现有的业务组件就能完成一半页面时,你会庆幸当初把这部分资产留了下来。