ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

从零搭建团队组件库:API设计与文档自动化实战指南

从零搭建团队组件库:API设计与文档自动化实战指南 先声明一句这篇文章不是讲怎么用某个现成组件库而是讲怎么从零搭一个“给自己团队用、以后能一直复用”的组件库。重点放在 API 设计和文档自动化上因为这两块决定了组件库能不能真的被用起来、能不能长期维护。我自己在带前端团队的过程中前后搭过两套组件库第一套踩了不少坑第二套基本跑顺了下面这些内容都是实操记录不是教科书式的理论。如果你正准备在团队里推行组件库或者已经在维护一个“半死不活”的库这篇内容应该能帮你少走不少弯路。我会把思路、原则、代码示例、踩坑记录拆开讲尽量做到每个细节都能直接落地。1. 组件库设计的顶层思路先把“可复用”拆开看在动手写第一个组件之前得先搞清楚一个问题到底什么叫“高可复用”这个说法太模糊了不同团队的理解完全不一样。我见过不少人把“能抽公共代码”当成复用结果抽出来的东西改一个需求要连带改十个页面。真正的可复用在我理解里至少要拆成四个层次功能可复用、场景可复用、样式可复用、维护可复用。功能可复用是最基础的比如一个 Button 组件不同页面都要用抽出来大家都调用这就是功能复用。但光是功能复用还不够因为实际项目中会遇到各种“变体”——按钮可能有大中小三种尺寸、有主次危险三种类型、有图标按钮和文字按钮的差别。如果这些变体都靠外部写样式去覆盖组件库就退化成了一个 CSS 代码片段仓库算不上组件库。场景可复用更难一点。同一个组件要在不同业务场景下工作比如一个 Select 下拉选择器有的业务需要单选有的需要多选有的需要远程搜索有的需要支持创建新选项。如果这些场景都靠“往组件里堆 props”来实现组件的 API 会越来越臃肿最终变成一个大泥球。合理的做法是把基础交互抽出来把场景差异通过插槽或子组件的方式留出来。样式可复用指的是视觉规范的一致性。这里有个常见的坑把样式硬编码在组件内部导致业务方无法覆盖。我见过有些组件库为了图省事直接在 JSX 里写内联样式看上去开发很快实际上业务方想改一个间距都下不了手只能强行用!important去覆盖这是最差的体验。正确的做法是用 CSS 变量把设计令牌暴露出来让业务方可以在根节点覆盖变量值而不是去覆盖具体的样式规则。维护可复用是很多人容易忽略的点。组件库不是写完就完了它在后续两年里要持续迭代。如果你的 API 设计没有考虑到向后兼容每次升级都会导致业务侧大规模改动组件库的推广自然阻力重重。维护可复用的核心是保证 API 的稳定性和语义化同时通过版本管理机制把这些变化控制住。1.1 组件库的定位通用型还是业务型设计组件库之前先想清楚一个问题你做的是通用组件库还是业务组件库这两者的设计逻辑完全不一样。通用组件库面向的是所有前端团队比如 Ant Design、Element Plus 这类API 设计追求普适组件语义化强更新节奏受社区驱动。业务组件库则面向公司内部的特定业务比如电商、招聘、金融系统API 设计会直接贴合业务场景比如一个“订单状态标签”组件内部封装了状态到颜色、文案的映射这类组件在通用组件库里是不存在的。大多数团队一开始都想做通用组件库做着做着发现水平跟不上最后变成了一个“半吊子”。我的建议是如果团队规模不超过 20 人优先做业务组件库。把项目里重复度最高的场景抽出来比如表格、表单、弹窗、页面容器把重复的交互逻辑和样式规范沉淀进去收益更快、更容易被接受。通用组件库的战场早就杀红眼了不要拿自己的业余时间去对抗别人的全职工作。1.2 组件库的“心跳”API 设计优先级高于视觉实现很多人搭组件库第一件事是去写 Button 的样式研究圆角多大、阴影多重。这个顺序是反的。视觉实现是组件库的表皮API 设计才是骨骼。API 决定了别人怎么用你的组件用起来舒不舒服、会不会频繁踩坑全部取决于 API 设计。设计 API 时要先回答三个问题这个组件的使用者是谁哪些内容是高频需求哪些是低频但必须支持的特殊场景高频需求要提供最简洁的用法低频场景要留出扩展口子但不体现在默认用法里。比如一个 Modal 弹窗组件打开和关闭是最高频的应该用声明式属性open控制阻止背景滚动、按 ESC 关闭是低频增强可以通过配置项支持。API 设计还有一个容易被低估的维度渐进式复杂度。好的组件 API 应该允许用户从最简单的高频用法开始用着用着想要更高级的定制再逐步增加参数。如果一上来就要求用户理解十几个 props 才能跑起来这个组件的 API 设计就是失败的。1.3 为什么“文档自动化”不是锦上添花而是必需品再好的 API 设计没有好文档也等于零。很多业务团队的组件库代码写得很漂亮但 README 只有一句“xxx 组件”下方再无下文。这种组件库上线第一个月还有人愿意翻源码去用三个月后基本没人碰了因为用它的认知成本太高了。问题在于手动维护文档的效力极其有限。组件改了 props没人同步改文档文档很快就失真了。业务方照着文档使用却发现 API 对不上下一次就不信任这个组件库了。所以文档自动化不是一个效率优化方案而是组件库能不能被信任的关键基础设施。我后面会详细讲怎么从 TypeScript 类型定义里直接生成 API 文档、怎么用 Markdown 片段组织使用示例、怎么打通代码修改和文档更新的链路。这套东西搭建初期要花点时间但长期老说省下的沟通成本和时间成本完全值回票价。2. API 设计的核心原则让组件“用起来就顺”我在设计第二套组件库时给自己定了四条 API 设计铁律命名要符合直觉、状态要区分受控非受控、类型要严格、行为要可预测。下面逐个细说。2.1 命名与语义好 API 是“读出来就懂”的命名是所有 API 设计的起点。props 的名字取得好不好直接决定使用者会不会频繁查文档。有个简单的测试方法不看文档只看 props 名称猜一下它的作用如果超过一半猜不中说明命名有问题。命名要遵循几个基本原则。布尔类型用open、disabled、loading这类动词过去式或形容词不要用isOpen、canDisabled这类冗余前缀。事件回调统一用onXxx格式对应原生事件语义比如onChange、onSelect、onClose。数据走单向流用value和defaultValue区分受控与非受控状态。还有一个容易被忽略的细节是组件名前缀。设计组件库时组件名要避免和原生 HTML 标签冲突比如Button在 React 中和原生button视觉上有差异很多人会习惯性写button导致样式不对。业界通用做法是加前缀比如BButton、XButton、TButton前缀就是团队名的缩写。我在第二套库里选的是TButtonT 取自团队英文名首字母这样业务方一看就知道是自研组件不会和第三方库混淆。2.2 受控与非受控把状态控制权交还使用者受控和非受控是组件 API 设计里最核心也最容易出问题的环节。受控组件的状态由外部传入内部不维护状态非受控组件的状态由组件内部管理外部只提供初始值。拿 Input 组件举例。非受控模式Input defaultValue默认文案 /受控模式Input value{value} onChange{e setValue(e.target.value)} /设计时要同时支持这两种模式使用规则是传入value且不传onChange时包装警告这是个常见 bug 触发条件传了value但没维护状态更新组件会出现内部 value 和外部传入 value 冲突。第二个坑我踩过很多次后来统一用了一个内部帮助函数处理。下面是一个标准实现行为和 Ant Design、Arco 等主流库保持一致function useControllableState(controlledValue, defaultValue, onChange) { const [innerValue, setInnerValue] useState(defaultValue); const isControlled controlledValue ! undefined; const value isControlled ? controlledValue : innerValue; const setValue (next, ...args) { if (!isControlled) { setInnerValue(next); } onChange?.(next, ...args); }; return [value, setValue, isControlled]; }这段代码的核心思路是组件内部始终有一个 innerValue但如果外部传了 value就用外部的。这样组件不管在受控还是非受控模式下内部逻辑可以完全不用区分五种精神状态直接捋清了。后来很多组件都基于这个 hook 来管理状态。2.3 Props 设计要“少而精留有扩展”props 数量和组件的易用性往往成反比。每次在设计组件 API 时我会给每个 props 标注“使用频率”高频、中频、低频。高频的放最前面、提供最简洁的默认值中频可以在文档里详细说明低频的用extraProps或者...rest方式转发不要全部显式声明。interface TButtonProps extends React.ButtonHTMLAttributesHTMLButtonElement { /** 按钮类型 */ type?: primary | default | danger; /** 按钮尺寸 */ size?: small | middle | large; /** 是否加载中 */ loading?: boolean; }低频属性可以直接通过继承 HTML 属性透传用户需要原生能力时直接传onClick、type等不需要组件额外声明。实际使用中发现至少 20% 的 props 都可以用这个方式透传这样组件 API 不会失去灵活性。但注意一个点继承 HTML 属性时要避开同名冲突。比如type这个属性在原生 button 里是按钮类型submit/reset/button在组件里被我改成了视觉类型。冲突时怎么办我最后把原生属性通过nativeType暴露出去。不处理这个冲突的话用户会用得很难受。2.4 默认值与可定制性的平衡默认值设计太重要了。给一个 props 设计默认值相当于告诉使用者“这是最常见的用法你不用写这个参数”。默认值不对很多使用者会莫名其妙地踩坑。给默认值定“三有”标准有意义、有基础、有退路。比如 size 的默认值middle对应的视觉尺寸是所有设计规范里的基础尺寸这就是有意义。而像autoFocus这类行为型属性默认值最好是 false因为不是每个场景都需要自动聚焦这就是有退路。我的做法是默认从“最少惊讶原则”用户不传参时拿到的结果应该是团队设计规范里最常见、最不让人惊讶的形态。比如 Modal 的默认宽度是 520px居中显示mask 默认不可关闭这个符合大多数人的预期不需要额外记忆。2.5 组件粒度的取舍超大组件与超小组件的困境粒度设计是 API 设计的宏观层面。粒度太粗比如把一个 Table 做成了包含筛选、排序、分页、列配置、编辑、导出的大一统组件表面上用起来方便实际上每次微调都要深入源码灵活性几乎为零。粒度太细每个小功能都要写一堆组合代码使用成本直线上升。我的经验是把组件分成“原子”“分子”“组织”三层。原子是 Button、Input、Icon 这类基础元素分子是表单、搜索栏、标签组这类复合元素组织是页面容器、ProTable 这类完整区块。大部分跨项目的复用需求发生在“原子”和“分子”层“组织”层通常绑定具体业务场景适合做业务组件库但不太适合做通用组件库。跨层调用的规则是组织层可以依赖分子层分子层可以依赖原子层反过来不行。换句话说业务页面不应该直接 import 原子组件再拼出一堆重复逻辑这部分逻辑应该沉淀为分子组件被不同的页面复用起来。3. 用 TypeScript 当“单一事实来源”类型驱动 API 设计这是我在第二套组件库里做的最大改变所有 props 结构、默认值、类型约束都先用 TypeScript 定义组件的具体实现只是把这些定义落实。好处非常明显使用者在写代码时能获得智能提示编辑器会在编译前拦截错误用法同时这些类型定义为后面文档自动化提供了数据来源。3.1 从 props 声明到运行时校验单纯依赖 TypeScript 静态类型检查不够因为业务方的代码可能用的是类型文件比较老的版本或者他们自己通过ts-ignore跳过了一些检查。所以对于关键 props我建议在运行时也做防御性校验。拿 Modal 的 visible 举例if (__DEV__ typeof visible ! boolean) { console.error([TModal] visible 属性必须是 boolean 类型当前收到, typeof visible); }只在开发环境输出这类警告不阻塞生产构建。这些运行时警告能极大降低使用方的排查成本否则出问题时对方会先怀疑自己的业务代码而不是组件库浪费很多时间。3.2 用泛型约束复杂业务场景有经验的组件库设计者会用泛型大大提高组件的复用程度。比如一个表格列配置组件列配置数据结构用泛型约束每一列的字段名必须对应数据字段interface ColumnT { title: string; dataIndex: keyof T | string; render?: (value: any, record: T, index: number) React.ReactNode; } interface ProTablePropsT { columns: ColumnT[]; data: T[]; }这样写的好处是使用方把数据结构传给组件后columns 里引用的dataIndex如果写错编辑器里会看到红色下划线提示。我在实践中的体会是泛型是组件库 API 从“能用”到“好用”的分水岭特别是表格、表单这类和数据打交道的组件泛型设计多花两小时后面能省无数沟通成本。3.3 类型文件测试API 也需要单测这里分享一个比较少见的做法。基本所有团队会给组件做单元测试但很少会有人给“类型”做测试。实际上类型 API 的变化同样会破坏业务侧的代码所以我会在 CI 里加一条“类型测试”任务专门验证类型导出的正确性。做法是用 TypeScript 的tsd或者vitest的类型断言能力import { expectTypeOf } from vitest; import { ModalProps } from ../src; expectTypeOfModalProps()[visible].toBeBoolean();这样的代码可以保证未来重构组件时类型定义变了测试会立刻跑红防止低调地破坏 API 兼容性。刚开始引入这个流程时同事觉得多此一举后来真有一次改动 props 类型导致五处业务报错测试第一时间拦住大家就信服了。4. 文档自动化的技术选型与落地方案说完了 API 设计下面进入大多数组件库最薄弱的环节文档。我接触过的不少团队组件库的文档还停留在手写 README 或 Markdown 文件的阶段内容错漏百出。这里我把自己验证过的一条完整链路分享出来你大概率可以直接照着搭一套。4.1 文档站点框架VitePress 与 Storybook 的取舍文档框架领域目前主流方案是Storybook和VitePress Vue 技术栈还可以考虑dumi或Vitepress。我的选择是 VitePress有几个实际原因。Storybook 的交互式文档很强大你可以直接在页面里调整 props实时看组件变化这对体验型组件如动画、表单交互很有价值。但 Storybook 的缺点是搭建成本高、自定义页面布局麻烦、构建比较慢而且对于面向业务的组件库来说我们更需要的是“查阅型文档”而不是“演示型文档”——大多数使用场景是我忘了这个参数是干嘛的来查一下而不是来玩组件的。VitePress 的好处是纯静态站点生成基于 Markdown轻量、快速、可定制性高构建产物就是一堆静态 HTML部署到任何静态资源服务器就行。配合 Vite 生态能直接在 Markdown 里写 Vue/React 组件实例做 Demo。我最后选了 VitePress 作为文档站点。4.2 从 JSDoc 到 API 表格自动生成 Props 文档文档自动化最核心的一步让 API 表格不再手写而是从 TypeScript 定义中自动提取。这样组件改了 props文档就跑不了两边永远同步。我用的方案是react-docgen-typescript如果是 Vue 项目可以看vue-docgen-api。它可以扫描组件的 TypeScript 类型定义和 JSDoc 注释解析出来一份结构化的 JSON包含每个 props 的名称、类型、默认值、注释。把这个 JSON 加工成表格就能渲染成文档页面。具体流程是在编写组件源码时每个 props 都写好 JSDoc 注释。文档构建时调用 react-docgen-typescript 扫描源码。扫描结果渲染成 Markdown 表格嵌入到文档页面中。拿前面的 TButton 举例源码里这样写interface TButtonProps { /** * 按钮语义类型 * default default */ type?: primary | default | danger; /** * 按钮尺寸 * default middle */ size?: small | middle | large; }文档构建链路会自动产出下面这个表格属性类型默认值说明typeprimary | default | dangerdefault按钮语义类型sizesmall | middle | largemiddle按钮尺寸这里有一个工程关键点JSDoc 注释里必须写default否则表格里默认值就是空。不少团队从 hand-written 文档切到自动生成时会发现之前注释写得不规范导致表格大量-补全注释本身也是一个提升文档意识的过程。4.3 Markdown 中内嵌可运行 DemoAPI 表格解决的是“参数是什么”但用户还需要“这个组件长什么样、怎么用”。这块需要在文档中内嵌可运行的 Demo。VitePress 支持在 Markdown 中直接使用.vue或.tsx组件。我为每个组件设计了一套 demo 文件文件名规则是demo/basic.tsx、demo/status.tsx、demo/size.tsx在文档中这样引用## 基本用法 最基础的按钮组件。 :::demo 基础按钮 tsx import { TButton } from tiny-ui; export default function BasicDemo() { return TButton主要按钮/TButton; }:::为了让 demo 能被直接引用、又能显示源码我写了一个 VitePress 插件扫描 :::demo 代码块把代码块内容提取出来通过 vitejs/plugin-react 实时编译渲染成组件同时把源码展示在代码预览区域。这样用户可以在文档里看到组件的实际渲染效果也能一键复制代码。这个插件大约 150 行代码工作量不大但做完之后文档的可用性提升了一个量级。 ### 4.4 版本化文档与变更日志自动化 组件库迭代到一定阶段会出现一个麻烦文档只展示最新版的使用方式但老版本的用法在某些业务项目里还在跑。如果老用户看到新文档后直接改代码可能踩到升级的坑。 解决方案是给文档站点构建多版本目录。比如 /v1/、/v2/每个版本对应一个分支或标签上的文档构建产物。我在 CI 里配置了一个 releases 流程每打一个新的 git tag就自动触发一次文档构建输出到对应的版本路径下版本切换入口放在文档导航栏上。 变更日志也不要手写直接用 conventional-changelog 从 commit message 中生成。前提是团队 commit 信息必须遵循约定式提交规范feat、fix、breaking 都是标准前缀。这套东西能保证“版本更新了changelog 同步更新文档同步更新”三个动作绑定在一起不会出现版本和文档错位的情况。 ### 4.5 Demo 即测试把文档用例接入单元测试 这个思路是我后期加的但收益非常大文档里的每个 demo 代码同时也是组件的单元测试用例。我把所有 demo 文件收集起来通过 testing-library/react 逐个渲染执行基础断言——能正常渲染不报错、点击后行为符合预期。这样文档里的示例不会出现“复制下来跑不通”的尴尬情况。 接线方式不复杂 - 源码目录里 docs/demo 下所有文件名以 .tsx 结尾的文件。 - 测试文件读取 demo 文件动态渲染。 - 断言的触发页面无报错demo 首次 render 成功。 这个方案一石二鸟文档里的每个示例都被真实执行过造假的可能性为零测试数量也大幅增加顺手提升了组件库的稳定性。实际跑下来demo 的报错率降低了很多业务方来反馈“文档示例有问题”的次数基本清零。 ## 5. 构建、发布与维护组件库能走多远的底层保障 文档自动化解决了“有人用”的问题构建和发布机制则解决“用得稳”的问题。这里虽然偏工程化但它决定了组件库最终能被多少项目顺利引入。 ### 5.1 构建产物设计ESM、CJS 与按需加载 组件库的构建产物通常要同时支持两种模块格式ESM给现代打包工具用和 CJS给 Node 与老构建链路用。我用 Vite 的 library 模式构建组件库产物目录大致如下dist/ ├── es/ # ESM 格式输出 ├── lib/ # CJS 格式输出 └── types/ # 类型声明文件package.json 里对应配置 json { main: lib/index.js, module: es/index.js, types: types/index.d.ts }按需加载方面社区方案有很多种最常见的做法是借助打包工具的 tree-shaking 能力。前提是产物的模块结构足够“干净”。我在组件库里用的是“每个组件一个文件”的结构这样打包工具可以精确地只打包被 import 的组件连带依赖的样式文件也分开输出。5.2 全量导入与按需导入的用户体验平衡全量导入和按需导入其实是两条路。对内部团队来说我建议默认全量导入简单省心import { TButton, TModal } from tiny-ui;如果项目特别在意首屏体积再切换到按需方式import TButton from tiny-ui/es/components/button;这里有个非常重要的细节样式文件应该随着组件导入自动加载而不是要求用户手动引入样式。做法是每个组件在入口文件里显式 import 它自己的样式文件例如import ./style.less;Vite/Rollup 在构建时会把样式抽取成独立的 CSS 文件并保留 import 关系现代打包器能自动处理。老一代方案如 babel-plugin-import现在已经不太需要了ESM tree-shaking 已经成了标配。5.3 语义化版本与 breaking change 管理组件库一旦被多个项目依赖版本管理就是一件严肃的事。我的推荐策略是严格执行语义化版本主版本号用于破坏性变更次版本号用于新增功能补丁号用于修复 bug。破坏性变更不仅指 API 删除或改名还包括默认行为的变化、视觉结构的重大调整、对旧版浏览器支持的降级等。这地方容易有争议团队会觉得“改个默认值不算破坏性变更吧”但从使用者的角度来说默认值变了页面渲染效果变了这就是破坏性变更应该走 major 版本。为了遏制“悄咪咪做破坏性变更”的冲动我会要求所有 pr 标题必须带上对应的变更类型标签没有标签的不允许合并到主分支。5.4 从 npm 私有包到项目接入发布流程设计组件库的发布要设计成一条自动化流水线每一步都留痕维护者发起 release PR同时更新 changelog 和版本号。CI 执行完整测试包括类型测试和 demo 用例测试。通过后合并到 main 分支触发构建。构建产物发布到公司 npm 私有仓库。打 git tag并同步触发文档站点构建与版本化部署。整套流程走下来业务方升级时的体验是版本号更新后先去对应版本文档看 changelog明确破坏了什么然后执行升级。这个链路跑通之后我很少再收到“升级后不知哪里出错”的反馈。6. 文档自动化实践细节与可视化效果增强文档站点如果只是“能看”还远远不够“好用”才是目标。这块我整理了几个提高使用效率的实践细节。6.1 把 demo 代码做成可折叠源码面板默认打开文档只显示组件的渲染效果源码是折叠的。这样用户看文档时不会被代码块打扰第一眼看到的是视觉形态想用的时候点一下源码标签就能展开。这个交互体验在内部反馈中好评度非常高几乎没人用回旧版文档。实现要点在 VitePress 里自定义容器组件:::demo解析后渲染成一个 Tab 组件一个 Tab 是“预览”另一个 Tab 是“代码”默认只展示预览。这个交互细节虽然小但对文档的浏览效率影响非常大。6.2 属性搜索与按需跳转组件库组件多了以后用户往往记得组件名但找不到入口或者在页面上想搜索一个 props 又不想下拉翻列表。我提供了两套搜索机制一套是组件级别的搜索框直接匹配组件名和组件概述输入后跳转对应页面另一套是页面内 API 表格的快捷搜索动态过滤表行。实现方案组件级搜索利用 VitePress 内置的搜索插件页面内 API 表格过滤是写了一个工具组件把 markdown 表格数据化顶部输入关键字过滤。这个工具组件沉淀了大概 80 行代码但带来的体验提升很明显特别是表格属性超过 20 行之后没有搜索等于让用户做人工二分查找。6.3 组件的“使用频次”标注给每个 props 的 API 表格里加一个可选的“频次”标记常用、一般、少用。这样使用者在查阅时眼睛会先扫到“常用”那一列不用在低频属性里大海捞针。这个标注跟 JSDoc 注释走写在类型定义里。/** * 按钮尺寸 * default middle * freq common */ size?: small | middle | large;freq 字段提取到表格后可以用不同背景色高亮“常用”属性。这个功能在我团队内部反馈中最受欢迎他们都说“以后改老页面再也不用通读一遍所有 props 了”。6.4 兼容性信息自动联动减少“我们环境跑不了”的问题组件的浏览器兼容性信息也做到文档里。我在 JSDoc 里加了一个since字段表示组件从哪个版本开始支持再通过构建时插件读取 target 浏览器列表在组件文档头部生成一个兼容性标签比如“支持 Chrome 80Edge 80Safari 14”。使用者一眼就知道这个组件能不能在自己的目标浏览器下使用不用自己猜。这个字段的信息来源于你构建配置文件里的 browserslist 设置会自动同步不需要手动维护。加上之后业务方少了很多“为什么我在 IE 里打开组件挂了”这类问题因为兼容性信息就摆在页面上。7. 组件库实操中的避坑经验与排查思路最后这部分单独讲实战里最容易出问题的地方。这些坑我在两套组件库里都踩过避免一次就能省下很多时间。7.1 样式隔离与设计令牌管理组件库最常被业务方吐槽的是“样式一升级业务页面全变样”。根源是组件样式和业务样式互相渗透。解决方法是把设计令牌Design Token统一管理起来所有颜色、间距、字号、圆角都定义为 CSS 变量:root { --tiny-color-primary: #006bff; --tiny-spacing-md: 16px; --tiny-font-size-base: 14px; }组件内部一律引用这些变量不写死数值。业务方如果要定制主题只要在页面根节点重新赋值这些变量即可不需要去覆盖组件内部的样式规则。这样组件升级时只要变量名不变视觉变化就能得到控制。有个细节变量命名要带前缀如--tiny-防止和第三方库的样式变量冲突。我见过一个团队用了不带前缀的--color-primary结果和另一个组件库冲突页面颜色变得乱七八糟排查了半天才发现是变量名被覆盖。7.2 “类名从一而终”样式覆盖中的结构稳定组件库迭代时DOM 结构变化最大的风险之一是业务方依赖的类名被改掉了。为了保证样式覆盖的稳定性我为每个组件的每块功能区块定义了稳定的类名BEM 命名并且把类名结构视为 API 的一部分。语义化版本里类名和 props 一样不会在 minor 版本里做破坏性调整。组件内部 DOM 结构变化只影响样式实现但类名和层级关系要尽量保持不变。这条原则在实际维护中很难一直坚持因为重构时总想顺手调整结构但在组件库里“顺手”是最大的敌人。7.3 常见问题速查构建、使用、升级最易踩的坑下面把我在实践里遇到的高频问题整理成一张速查表遇到相关问题可以按图索骥现象可能原因解决方案组件库样式完全没生效样式文件没被 import打包器未加载 CSS import检查组件入口是否引入了样式文件在打包器配置中开启 CSS 处理编辑器提示找不到类型声明types 字段没有正确指向类型产物目录检查 package.json 的 types 字段发布时确认 types 目录是否在 files 白名单中升级小版本后组件样式发生变化设计令牌变化样式结构调整查看 changelog 中样式变更部分统计页面上是否覆盖了旧的令牌变量props 明明传了却没有效果非受控模式下 setState 被忽略受控值被内部状态覆盖检查组件是否处于受控模式确认事件回调是否正确触发查看 isControlled 逻辑同一页面使用两个组件库类名冲突两个组件库均未做类名前缀隔离至少一套使用带前缀的类名方案检查样式作用域隔离文档 demo 报错但本地运行正常demo 依赖了未声明的全局变量或 mock 数据审查 demo 中依赖的全局环境在测试环境统一注入 mock打包构建体积翻倍组件库产物被重复打包或未正确 tree-shaking开启 bundle 分析确认 package.json 的 module 字段指向 ESM 产物7.4 排查链路从组件内部发布警告到业务侧定位当业务方反馈“组件行为不对”时我主要按三个步骤排查第一确认业务方的组件版本。很多时候问题已经在最新版修复了但业务项目中锁定了老版本。这个步骤排掉至少 30% 的无效排查。第二看开发环境警告。组件库内部合理的警告能帮助业务方定位问题所以业务方应该开着控制台检查把所有警告贴给维护团队。好的组件库的第一道防线就是这些警告。第三写最小复现。我一般会让业务方提供一个最小可复现仓库或者直接在 Storybook/demo 里复现。官方维护者应该维护一个“问题复现模板”遇到问题先让反馈者填这个模板能省去很多来回沟通成本。7.5 团队协作中的体验建立组件库“使用反馈群”组件库维护者和使用者之间如果缺少沟通机制组件库就会越来越偏离真实需求。我在团队里建了一个“组件库反馈群”定了几条规则任何“这个组件怎么用”的提问优先引导到文档中找答案而不是在群里直接回答。时间久了文档的覆盖率会越来越高常见问题都被写进去。任何“这个组件缺一个功能”的反馈必须带上使用场景的说明最好附上设计稿或页面截图否则不予排期。每季度做一次组件库版本升级动员会把最新版本的特性、breaking change 一起同步给业务方。这套机制让组件库的迭代方向一直贴着业务走而不是维护者拍脑袋写组件。这算是工程之外但非常关键的一点经验——文档自动化和 API 设计能解决“好不好用”反馈机制才解决“用不用得上”。8. 二轮迭代基于反馈的组件库演进组件库不能一次性设计到位它更像是在使用中不断被“蹂躏”、然后变强的过程。我在这里整理几个典型的演进方向。8.1 从通用组件向业务场景包演进第二套组件库用了一年左右业务侧开始高频复现一些跨组件的组合模式比如“搜索表单 结果表格 分页器”每个页面都重新编排一遍代码重复率非常高。这时候我们在分子层之上加了几个“场景组件”直接把这个组合模式封装起来。做成的ProSearchPage组件内部组合了表单和表格通过配置数组生成整个页面布局业务侧传入 api 请求函数即可。这类组件是通用组件库永远不会做的但在业务组件库里是效率利器。8.2 响应式与暗黑模式的支持设计组件库时就要为暗黑模式预留空间而不是等需求来了再硬加。我在设计令牌层面分了 light/dark 两套变量默认跟随系统prefers-color-scheme同时支持在页面根节点手动切换主题。后续所有组件都通过引用变量自动适配不需要每个组件写两套样式。这个决策在初期要多花一些时间在改造设计变量上但后续需求来了就是“配置一下变量表”的事几乎零成本。8.3 可访问性a11y的预埋组件库的键盘导航、焦点管理、ARIA 属性也需要在设计期考虑。比如 Modal 关闭按钮的aria-label、Form 校验错误信息的aria-describedby、Select 下拉列表的aria-expanded等等。这些属性如果没有预埋后期补起来极其痛苦因为涉及大量 DOM 结构调整而 DOM 结构调整最影响类名继承稳定性。我在每个组件的设计文档里都加了一个“Accessibility Checklist”小节列出这个组件必须支持的键盘操作和 ARIA 要求。交付时 code review 会专门看这一块做到了再合并。9. 最后再分享一个效率技巧为组件库搭建“本地开发沙箱”很多组件库维护者都会遇到一个问题改一个组件怎么快速看它和别的组件配合时的效果只预览单个组件很难发现组合场景下的布局问题。我做了两件事解决了这个痛点一是写了一个“组件列表页”本地开发服务器启动后首页会列出所有组件及其 demo相当于一个私有 Storybook。点进去就能在完整页面上下文中预览组件。二是在 VitePress 里给每个组件都配了一个“独立预览页”用 iframe 隔离样式环境避免组件之间的样式互相干扰。这个改造对我的调试效率提升很大特别是定位样式冲突问题时iframe 隔离是快速确认“是不是组件间互相污染”的利器。组件库的开发体验和最终使用者体验同等重要。维护者开发时感到顺畅才有意愿做更多优化如果维护者自己都觉得难改那这个组件库迟早会死掉。我在这套组件库上最大的体会是API 设计决定这个库的上限文档自动化决定下限能不能守住构建维护机制决定它能不能活过第一年。三者缺一不可。如果你正准备启动组件库项目可以从这三个方面同时用力别只盯着写组件本身。
返回列表