企业级 UI 组件库设计:Token 系统 / 主题定制 / 文档体系

0 0

前言

做前端架构的人要不要懂组件库设计?我的回答是 ——不是去抄 Element Plus / Ant Design,而是建立”50 个组件怎么保持 API 一致性、视觉统一性、可维护性”的工程哲学。原因有三:

  1. 简历里 iView / FinUI 是真实项目设计 Token 体系 / 主题定制 / 文档化 决定组件库能用多久。
  2. 组件库是团队的”基础设施”。50 个业务方依赖你的组件库,改一个 API = 改 50 个项目的代码架构师必须有”破坏性变更控制”思维
  3. 可访问性 / 国际化 / 暗色模式 是组件库的必答题,不是选答题。Element Plus v3 / Ant Design v5 都在强调这几点

这一篇是『前端架构修仙路』的第 23 篇。我跳过具体组件 API(不讲 Button 怎么写),用设计哲学 + 真实经验 + 决策表,把 Token 体系 / 主题 / 文档 / API 一致性 / 版本管理讲透。下一篇深度聊中后台开发模式。

一、组件库三大核心目标

图 1:组件库三大目标

二、Design Token:组件库的灵魂

2.1 Token 三大层级

// 1. 基础 Token(原子层,不变)
const baseTokens = {
  'color-blue-500': '#1a73e8',
  'color-blue-600': '#1557b0',
  'space-1': '4px',
  'space-2': '8px',
  'radius-sm': '4px',
  'radius-md': '8px',
  'font-size-sm': '12px',
};

// 2. 语义 Token(基于基础 Token,可换主题)
const semanticTokens = {
  'color-primary': 'var(--color-blue-500)',
  'color-primary-hover': 'var(--color-blue-600)',
  'spacing-small': 'var(--space-2)',
  'radius-default': 'var(--radius-md)',
  'text-sm': 'var(--font-size-sm)',
};

// 3. 组件 Token(具体组件,默认用语义 Token)
const buttonTokens = {
  'button-primary-bg': 'var(--color-primary)',
  'button-primary-bg-hover': 'var(--color-primary-hover)',
  'button-padding': 'var(--spacing-small)',
};

架构师心法业务方只引语义 Token——color: var(--color-primary) 而不是 var(--color-blue-500)主题切换时只改 baseToken——蓝变绿一键搞定。

2.2 主题切换实战

// 暗色模式:换 baseToken
:root[data-theme="dark"] {
  --color-blue-500: #4a9eff;     /* 暗色蓝要更亮 */
  --color-blue-600: #6bb0ff;
}

// 客户主题定制:比如医院绿色主题
:root[data-theme="hospital"] {
  --color-blue-500: #00a878;     /* 医院绿 */
  --color-blue-600: #008866;
}

生产案例:iView 改 Ant Design 风格时,只换了 baseToken——3 天迁移完成。没 Token 体系的组件库 = 改一个颜色改 200 个文件

三、API 一致性:组件库的灵魂

3.1 五大一致性原则

所有组件遵循:
1. props 命名:size / type / variant / disabled
2. 事件命名:onClick / onChange / onSubmit
3. 状态:loading / disabled / readonly / error
4. 受控/非受控:value / defaultValue 都支持
5. 回调参数:(value, event) 顺序统一
// ✅ 一致性
<Input value={val} onChange={setVal} size="small" disabled />
<Select value={val} onChange={setVal} size="small" disabled />
<DatePicker value={val} onChange={setVal} size="small" disabled />

// ❌ 不一致(灾难)
<Input val={val} onChangeVal={setVal} small />
<Select v={val} onSel={setSel} size="small" />
<DatePicker val={val} onDateChange={setVal} />

架构师规则API 一致性 > 单一组件的完美性。一个组件做得很完美但 API 与其他组件不一致 = 团队学习成本 × 50。

四、文档与示例:Storybook

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
  title: 'Components/Button',
  component: Button,
  argTypes: {
    size: { control: 'select', options: ['small', 'medium', 'large'] },
    variant: { control: 'select', options: ['primary', 'secondary', 'danger'] },
    disabled: { control: 'boolean' },
  },
};
export default meta;

type Story = StoryObj<typeof Button>;

export const Primary: Story = {
  args: { variant: 'primary', children: 'Primary Button' },
};

export const Secondary: Story = {
  args: { variant: 'secondary', children: 'Secondary Button' },
};

Storybook 三件套

  • 每个组件有 stories 文件
  • Controls 面板可以交互调试 props
  • Docs 页面自动生成 props 表格

五、版本管理与 Breaking Change

5.1 SemVer 规则

major: 破坏性变更(API 不兼容)
minor: 新功能(向后兼容)
patch: Bug 修复

5.2 Breaking Change 控制

// v1.x Button
<Button onClick={handleClick}>Click</Button>

// v2.0 加 size prop(非破坏性,minor)
// v3.0 onClick 改名为 onPress(破坏性,major)

/* ⚠️ 必须在 v2.x 文档中预告 + 提供 codemod */
<Button onPress={handlePress}>Click</Button>

// codemod.ts: 自动迁移
transform: (file, api) => {
  const j = api.jscodeshift;
  return j(file.source)
    .find(j.JSXElement, { openingElement: { name: { name: 'Button' } } })
    .forEach(path => {
      // onClick → onPress
    })
    .toSource();
};

架构师心法破坏性变更 = 用户升级 = 团队信任Codemod 自动化迁移让用户升级 = 跑一行命令。

六、可访问性(A11y)

// ✅ 关键组件都加 a11y 属性
<button aria-label="关闭弹窗" aria-expanded={isOpen} aria-controls="modal-content">
  <X />
</button>

<select aria-required={required} aria-invalid={!!error}>
  <option>...</option>
</select>

WCAG 2.1 AA 必填项

  • 颜色对比度 ≥ 4.5:1(普通文字)/ 3:1(大文字)
  • 键盘可访问:Tab 顺序合理 / Enter / Space / Esc / 方向键
  • aria-label / aria-describedby:屏幕阅读器可读
  • focus ring 可见:键盘焦点不能消失

七、生产实战:iView / FinUI 设计经验

iView(柔宇时期)

  • 2016 年开始维护,30+ 组件
  • 2018 年 v3.0 重构为 Vue 单文件组件
  • 2024 年迁移到 Vue 3 + Composition API
  • 核心经验Design Token 一定要从 day 1 设计——后期加 Token 体系 = 重写 50 个组件

FinUI(顺丰时期)

  • 50+ 组件 + 业务组件库
  • 大表格 FinSpread(已并入 FinUI)
  • 主题定制支持多家客户品牌色
  • 核心经验API 一致性 PR review 必查——v2 改 size prop 后被投诉”为什么 table 不支持”,团队重写 30 个组件对齐 API

八、踩坑提醒(资深架构师请重点看)

  1. 不要从 day 1 就建 100 个组件先做 5-10 个核心组件(Button / Input / Select / Table / Form),业务用起来后再加
  2. 不要用同一个 prop 名表达不同含义size="small" 在 Button 是 padding,在 Table 是字体大小——应该分开命名(Button 用 size,Table 用 density)。
  3. 不要忽略 A11yWCAG 2.1 AA 是企业级组件库的入场券——政府 / 医疗 / 银行客户必查。
  4. 不要让 breaking change 太突然提前 1 个 minor 版本预告 + 提供 codemod + 文档示例,让用户有时间迁移。
  5. 不要忘记 bundle size每个组件都要 tree-shakable——按需引入 import { Button } from 'finui' 而不是 import * from 'finui'

总结

这一篇用 6 个关键事实把组件库设计串起来:

  • Design Token 三层架构:基础 / 语义 / 组件 Token,主题切换只改基础
  • API 一致性 > 单组件完美:所有组件同款 props / 事件 / 状态命名。
  • Storybook 文档:每个组件 stories + Controls 调试 + Docs 自动生成。
  • SemVer + Codemod:Breaking change 必预告 + 自动迁移
  • WCAG 2.1 AA 是入场券:键盘 / aria / 颜色对比度全要。
  • 生产经验:iView 30+ 组件、FinUI 50+ 组件,核心都是Token 体系 + API 一致性

下一篇:中后台开发模式——表单 / 表格 / ECharts,800+ 页面 ERP 沉淀。

5 道重点面试问题方向

Q1(答案):Design Token 三层架构是什么?为什么基础 / 语义 / 组件要分开?

A:三层架构:① 基础 Token(原子层) — 颜色 / 间距 / 字号 / 圆角的具体值,如 color-blue-500: #1a73e8;② 语义 Token(基于基础) — 业务场景的别名,如 color-primary: var(--color-blue-500);③ 组件 Token(具体组件) — 组件内部用,如 button-primary-bg: var(--color-primary)为什么分开:业务方只引语义 Token,主题切换时只改基础 Token案例:某组件库没 Token 体系,改暗色模式要改 200 个文件;有 Token 体系后改 1 个 baseToken 切换全主题,3 天迁移完成。

Q2(思考):API 一致性 vs 单组件完美,哪个更重要?怎么保证 50 个组件 API 一致?

A:API 一致性 > 单组件完美反例:某组件库 v2 各组件 props 命名混乱(Input 用 val / Select 用 v / DatePicker 用 value),团队学习成本 × 50保证一致性的方法:① PR review 必查 — 新组件必须和已有组件 props 命名一致;② codemod 工具自动对齐 — 旧组件被新组件风格统一;③ API 文档化 — 内部 wiki 列出所有组件的 props / 事件命名;④ 设计 Token 体系约束 — 让”颜色 / 间距”无法自定义,强制走 Token。

Q3(思考):Breaking Change 怎么处理?Codemod 的作用是什么?

A:Breaking change = 用户升级成本正确处理:① 提前预告 — 在下个 minor 版本文档标注”v3.0 will change X”;② 提供 codemod 工具 — 用户跑 npx @finui/codemod v2-to-v3 自动迁移;③ 保留兼容期 — 旧 API 在 major 版本前继续工作,只 deprecate 警告;④ 文档示例 — 给升级前后的代码 diff。Codemod 的作用:自动重写代码。某组件库 v2 改 size 命名,5 个项目 30 万行业务代码,1 个 codemod 跑 1h 完成升级——没有 codemod 要手动改 3 周

Q4(思考):WCAG 2.1 AA 对企业级组件库意味着什么?哪些是必做项?

A:WCAG 2.1 AA 是企业级组件库的入场券——政府 / 医疗 / 银行客户必查,不合规不能上他们的项目必做项:① 颜色对比度 ≥ 4.5:1(普通文字) / ≥ 3:1(大文字);② 键盘可访问 — Tab / Shift+Tab / Enter / Space / Esc / 方向键都要支持;③ aria 属性 — aria-label / aria-describedby / aria-expanded / aria-controls / aria-required / aria-invalid;④ focus ring 可见 — 键盘焦点不能消失(不要 outline: none);⑤ 屏幕阅读器测试 — 用 VoiceOver / NVDA 实际测试。

Q5(思考):你在 iView / FinUI 的组件库设计里,有什么具体的经验教训?踩过哪些坑?

A:iView(柔宇,30+ 组件):① Design Token 一定要 day 1 设计 — 后期加 Token = 重写 50 个组件;② Vue 2 → Vue 3 迁移 踩坑多,Composition API + <script setup> 让 30 个组件代码减半。FinUI(顺丰,50+ 组件):① API 一致性 PR review 必查 — v2 改 size prop 后被投诉”为什么 table 不支持”,重写 30 个组件对齐;② 大表格 FinSpread(300 列 × 500 行)用虚拟滚动 + 列虚拟化,从 800ms 优化到 50ms;③ 跨客户主题定制 用 Design Token 体系,3 天切换品牌色共同教训:Breaking change 必预告 + codemod 自动化——信任建立一次需要 3 年,毁掉一次只要 1 个 release

💡 每道题后面都有 AI 助手按钮,一键拿到详细答案。

参考资料

🔗 原文链接 分享让更多人看到

评论