2024-09-18-【架构】Vue3组件库设计开发
一、UI 组件库设计
在 Vue 生态中实现 UI 组件库架构设计,本质上是构建一套可维护、可扩展且高性能的资产生产线。
一个成熟的 UI 框架架构通常分为:核心驱动层(Core)、组件实现层(Components)、构建工具链(Tooling)和文档/示例层(Docs)。
1. 整体架构设计图
graph TB
subgraph A [核心驱动层 Core]
A1[设计令牌 Design Tokens]
A2[主题系统 Theme System]
A3[工具函数 Utilities]
A4[类型定义 Type Definitions]
end
subgraph B [组件实现层 Components]
B1[原子组件 Atoms]
B2[分子组件 Molecules]
B3[有机体 Organisms]
B4[业务组件 Business]
end
subgraph C [构建工具链 Tooling]
C1[Vite + Rollup]
C2[Tree-shaking]
C3[TypeScript编译]
C4[样式预处理]
end
subgraph D [文档/示例层 Docs]
D1[VitePress文档]
D2[Storybook示例]
D3[Playground演示]
D4[自动化测试]
end
A --> B
A --> C
A --> D
B --> D2
C --> D1
C --> D3
2. 核心架构分层
graph TD
A[基础配置层 Core/Tokens] --> B[组件实现层 Components]
B --> C[逻辑抽离层 Headless & Composables]
subgraph A [基础配置层]
A1[CSS变量系统]
A2[设计令牌 Design Tokens]
A3[主题配置 Theme Config]
A4[间距/颜色规范]
end
subgraph B [组件实现层]
B1[原子组件 Atoms<br/>Button, Input, Icon]
B2[分子组件 Molecules<br/>SearchBox, Select]
B3[有机体 Organisms<br/>Table, DatePicker]
end
subgraph C [逻辑抽离层]
C1[useSelect Hook]
C2[usePagination Hook]
C3[useTable Hook]
C4[业务逻辑 Composables]
end
A1 --> B1
A2 --> B2
A3 --> B3
C1 --> B2
C2 --> B3
C3 --> B3
A. 基础配置层 (Core / Tokens)
不要在组件里写死颜色或间距。使用 Design Tokens(设计令牌)来管理视觉规范。
- 实现方式:定义一套全局 CSS 变量或 SCSS/Less 变量。
- 功能:支持主题切换(深色模式)、统一的圆角、阴影和间距标准。
B. 组件实现层 (Components)
采用”原子化”设计思路:
- 原子组件 (Atoms):Button, Input, Icon, Tag。
- 分子组件 (Molecules):SearchBox(Input + Button), Select。
- 有机体 (Organisms):Table(包含 Checkbox, Pagination, Loading), DatePicker。
C. 逻辑抽离 (Headless & Composables)
利用 Vue 3 的 Composition API 实现”无头(Headless)”逻辑。
- 思路:将复杂的逻辑(如 Select 的选中、键盘导航、分页算法)抽离成独立的
useSelect或usePagination。 - 优点:如果未来要改 UI 样式,逻辑逻辑层可以 100% 复用。
3. 组件库的目录结构 (Monorepo)
graph TD
Root[项目根目录] --> Packages[packages/]
Root --> Docs[docs/]
Root --> Playground[playground/]
Packages --> Components[components/]
Packages --> Theme[theme-chalk/]
Packages --> Utils[utils/]
Packages --> Constants[constants/]
Components --> Button[button/]
Components --> Input[input/]
Components --> Table[table/]
Components --> ...[...]
Button --> Src[src/]
Button --> Index[index.ts]
Src --> VueFile[Button.vue]
Src --> Test[Button.test.ts]
Src --> Style[Button.scss]
Docs --> Config[配置文档]
Docs --> Examples[使用示例]
Docs --> API[API文档]
Playground --> Demo[演示页面]
Playground --> Dev[开发环境]
Playground --> TestEnv[测试环境]
推荐使用 pnpm + Monorepo 架构,这样可以将样式、工具类、组件、文档拆分为独立的包。
1 | |
4. 关键技术选型
| 维度 | 推荐方案 | 理由 |
|---|---|---|
| 构建工具 | Vite + Rollup | Vite 开发快,Rollup 打包体积小且 Tree-shaking 强。 |
| 样式管理 | SCSS / Tailwind | SCSS 适合编写结构化变量;Tailwind 适合快速原型。 |
| 文档工具 | VitePress | Vue 官方支持,渲染速度快,直接在 Markdown 里写 Vue。 |
| 组件开发沙盒 | Storybook | 组件孤立开发、视觉测试、状态枚举;框架无关 (React/Vue/Angular/WebComponents) |
| 在线运行环境 | Playground (如 StackBlitz) | Bug 重现、示例演示、零环境上手,浏览器端 WASM / 远程 Container |
| 类型检查 | TypeScript | 提供完整的 IDE 自动补全,降低库使用者的出错率。 |
| 按需引入 | unplugin-vue-components | 用户无需手动 import,插件自动扫描并引入对应组件。 |
5. 架构设计的”避坑”准则
- 插槽(Slots)大于 属性(Props):
- 不要通过 Props 传太多的配置,尽可能通过
slot给用户扩展空间。
- 样式隔离:
- 使用 BEM 命名规范(如
.s-button--primary)或 CSS Modules,防止组件样式污染用户全局环境。
- **单向数据流与
v-model**:
- 确保所有组件支持 Vue 3 标准的
v-model,并在内部严格遵循”Props Down, Events Up”。
- Tree-shaking 友好:
- 导出格式应包含 ESM,并确保每个组件可以独立打包,配合
sideEffects: false配置。
6. 组件自动化产线 (The Workflow)
一个标准的开发流应该是:
- 命令创建:
pnpm gen:component <name>自动生成组件模板代码。 - 组件测试:使用 Vitest 或 Cypress 进行单元测试和交互测试。
- 视觉巡检:集成 Storybook,在不同屏幕尺寸和主题下查看组件表现。
- 发布流程:自动生成 Changelog,并同步推送到 npm 和文档站点。
二、业务组件库建设
在大型项目中,基于基础 UI 库(如 Element Plus, TDesign-vue-next、Ant Design Vue)封装业务组件库,核心目标是业务解耦、逻辑沉淀、视觉高度统一。
这种架构设计通常被称为 “Second-layer Component Library”(二次封装组件库)。以下是深度落地的架构设计方案:
1. 核心分层架构设计
graph TB
A --> B
B --> C
C --> D
subgraph A [基础 UI 层 Base UI]
A1[Element Plus]
A2[Ant Design Vue]
A3[其他第三方库]
end
subgraph B [业务抽象层 Service Abstract]
B1[权限控制逻辑]
B2[数据格式化]
B3[错误处理机制]
B4[统一埋点]
end
subgraph C [业务组件层Business Components]
C1[UserSelect 用户选择器]
C2[OrderTable 订单表格]
C3[SearchForm 搜索表单]
C4[其他业务组件]
end
subgraph D [应用层 Application]
D1[业务页面]
D2[管理后台]
D3[移动端应用]
end
A1 --> C1
A2 --> C2
B1 --> C1
B2 --> C2
B3 --> C3
B4 --> C3
style A fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
style B fill:#e8f5e8,stroke:#1b5e20,stroke-width:2px
style C fill:#fff3e0,stroke:#e65100,stroke-width:2px
style D fill:#e3f2fd,stroke:#0d47a1,stroke-width:2px
业务组件库不应只是简单的样式修饰,而应是具有”业务灵魂”的组合。
- 基础 UI 层 (Base UI):外部依赖(如 Element Plus),提供原子能力。
- 业务抽象层 (Service Abstract):封装通用的业务逻辑,如权限控制、数据格式化、统一的错误处理。
- 业务组件层 (Business Components):基于 Base UI 和业务逻辑组合而成的复合组件。
2. 关键设计模式
A. 属性透传与插槽转发 (Attribute & Slot Forwarding)
这是封装最核心的原则:不要破坏基础组件的原有能力。
- $attrs:使用
v-bind="$attrs"\将用户传给业务组件的所有属性直接透传给内部的 Base UI 组件。 - Slots:动态转发所有插槽,确保用户依然可以使用基础组件提供的自定义能力。
B. 配置化驱动 (Schema Driven)
对于复杂的业务组件(如查询表格、动态表单),采用配置化而非声明式。
- 实现:定义一个
columns或schemas数组,内部通过v-for配合渲染函数(h 函数)或resolveComponent动态渲染基础组件。 - 优点:显著减少业务页面的模板代码量,方便统一埋点和权限过滤。
C. Headless Logic (逻辑抽离)
将业务逻辑写成 Composables (Hook)。
- 例如:封装一个
useTableSearch,处理分页、查询、重置逻辑。 - 业务组件库只负责将这些 Hook 与 Base UI 的 UI 结合。
3. 工程化架构 (Monorepo)
推荐使用 pnpm + Turborepo/Lerna 的 Monorepo 模式,将仓库拆分为多个子包:
1 | |
4. 样式管理策略
在大型项目中,最忌讳的是 !important 满天飞。
- Design Tokens:在基础 UI 的变量之上,定义一层业务变量(如
--brand-color-primary)。 - BEM 规范:使用统一的前缀(如
s-user-card),避免与基础库或主应用冲突。 - CSS Variable 注入:通过一个
ThemeProvider组件,在根部注入业务主题变量,实现动态换肤和业务风格统一。
5. 解决”版本兼容”与”依赖”的策略
当基于基础库封装时,依赖管理是成败关键:
| 策略 | 实现方式 | 适用场景 |
|---|---|---|
| Peer Dependencies | 声明基础 UI 库版本号。 | 插件式组件库,要求主应用必须安装指定版本的 UI 库。 |
| Bundle Externals | 打包时不打包基础 UI 库代码,仅保留引用。 | 保持产物体积轻量,依赖主应用提供的运行时。 |
| Version Mapping | 在 package.json 中配置别名。 |
解决主应用与业务库 UI 版本冲突的紧急方案。 |
6. 自动化生产力工具
- 脚手架工具:一键生成组件目录、测试文件、文档入口(如
pnpm gen:comp)。 - 视觉回归测试:利用 Loki 或 Chromatic,在 CI 阶段自动比对组件封装后的样式是否发生意外偏移。
- 自动文档提取:利用
vue-component-meta自动从 TypeScript 定义中提取组件的 Props 和 Events,生成 API 文档。
三、包发布
在基于 Turborepo 的 Monorepo 架构中,发布子包的核心逻辑是:利用 Changesets 管理版本与日志,并通过 pnpm workspace 处理内部依赖,最后通过 CI/CD 自动化推送到 npm 仓库。
以下是标准化的发布流水线落地方案:
1. 核心工具链选型
在 Turbo 项目中,我们通常不直接使用 npm publish,而是采用以下组合:
- Changesets (推荐):负责版本控制、生成 Changelog、处理子包间的联动更新。
- pnpm workspace:管理子包间的依赖引用(如
workspace:*)。 - GitHub Actions / CI:实现全自动化发布。
2. 发布流水线流程图
flowchart TD
A[代码变更完成] --> B[运行 pnpm changeset]
B --> C[选择包与版本类型<br/>patch/minor/major]
C --> D[生成变更记录文件]
D --> E[提交变更到Git]
E --> F[CI/CD触发发布流程]
subgraph F [自动化发布流程]
F1[GitHub Actions运行]
F2[读取变更记录]
F3[自动更新版本号]
F4[处理内部依赖联动]
F5[生成CHANGELOG.md]
F6[turbo build构建]
F7[发布到npm仓库]
F8[清理临时文件]
end
F1 --> F2
F2 --> F3
F3 --> F4
F4 --> F5
F5 --> F6
F6 --> F7
F7 --> F8
F --> G[发布完成]
style F fill:#e1f5fe,stroke:#01579b,stroke-width:2px
style G fill:#c8e6c9,stroke:#2e7d32,stroke-width:2px
3. 第一阶段:初始化 Changesets
首先在根目录配置发布工作流:
- 安装依赖:
1 | |
- **配置
.changeset/config.json**:
确保access设置为public(如果是公开包),并配置好需要发布的包路径。
3. 第二阶段:开发与版本标记 (开发者流程)
当你完成了一个子包(如 packages/ui-business)的修改,需要发布时:
- 运行标记命令:
1 | |
- 选择包与类型:交互式选择哪个包更新了,是
patch(修复)、minor(新功能)还是major(破坏性改动)。 - 提交生成的 Markdown:Changeset 会生成一个临时文件记录变更,将其提交到 Git。
4. 第三阶段:发布流水线 (CI/CD 流程)
这是自动化发布的关键,通常在根目录的 package.json 中定义两个关键脚本:
1 | |
自动化发布逻辑:
- 版本更新 (
version-packages):
- 读取
.changeset/*.md。 - 根据标记自动更新所有子包的
package.json版本号。 - 自动处理内部依赖:如果
A包依赖B包,且B升级了,A的版本也会相应联动。 - 删除 Markdown 记录并生成
CHANGELOG.md。
- 构建与执行发布 (
release):
turbo build:利用 Turbo 的缓存能力,只编译有变动的包。changeset publish:遍历所有包,对比 npm 远端版本,自动执行npm publish。
5. 关键:解决内部依赖 (workspace:*)
在 Turbo 项目中,子包之间常互相引用:"ui-business": "workspace:*"。
发布时,Changesets 和 pnpm 会自动将 workspace:* 转换为真实的语义化版本号(如 ^1.2.0)。你不需要手动修改源码中的版本号。
6. 生产环境最佳实践 (GitHub Actions)
建议创建一个 .github/workflows/release.yml,实现”合并到 main 分支即发布”:
1 | |
💡 排雷指南
- 发布私有包:如果在公司内网(Nexus/Verdaccio 私有仓库),记得在子包的
package.json中配置publishConfig: { "registry": "你的私有库地址" }。 - 构建顺序:确保
turbo.json中配置了正确的dependsOn。如果业务库依赖基础库,发布前的build必须先完成基础库的构建。 - .npmignore:记得在子包中配置忽略
src、tsconfig.json等源码文件,只发布编译后的dist或lib。