ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

设计系统代码评审该看哪些细节

设计系统代码评审该看哪些细节 设计系统代码评审该看哪些细节设计系统的改动会被许多页面重复使用评审需要同时看组件行为、公共 API、样式作用域和发布产物。单元测试与 Lint 可以拦住一部分确定性问题却不知道一个属性是否构成兼容承诺也无法替消费者验证真实打包结果。检查应从“这段代码能否运行”扩展到“旧用法升级后是否仍成立”。1. 五处容易越过组件边界的改动1.1 副作用标记sideEffects与 Tree-shaking 破坏许多开发者在给组件库添加新功能时喜欢在组件顶层作用域执行全局注册或初始化逻辑例如// 危险代码顶层副作用阻断 Tree-shaking window.__MY_COMPONENT_REGISTRY__ window.__MY_COMPONENT_REGISTRY__ || []; window.__MY_COMPONENT_REGISTRY__.push(Button);顶层注册意味着导入模块就会改写全局状态。若包同时声明sideEffects: false打包器可能在未使用导出时删除这个模块导致注册逻辑根本不执行若为了保留它而把更大范围标记成有副作用又会影响 Tree-shaking。评审时应先确认注册是否必要再把必须执行的入口和纯组件模块分开声明。1.2 全局 CSS 变量污染与样式生效层级撕裂在编写组件样式Less/Sass/Tailwind时开发者常为了图省事直接修改全局根选择器/* 危险 CR 细节直接污染 :root 全局变量 */ :root { --primary-color: #1890ff; /* 覆盖了设计系统的统一 Token */ }组件内部不应悄悄覆盖全局 Token。不过设计系统本身可能需要在明确的主题入口定义:root变量因此不能只凭选择器一票否决。更有效的检查是确认全局样式是否来自约定入口、变量是否属于公开 Token、主题切换和嵌套主题是否有测试。1.3 组件 API 隐蔽的 Breaking Changes把可选属性改成必填、删除枚举值、调整回调参数和改变默认行为都可能破坏旧调用。库自身编译通过只能说明内部一致。评审需要对照上一个已发布版本的类型声明并用代表性的消费者样例做编译和交互测试。1.4 TypeScript 类型导出缺失与any漏网消费者需要组合或封装组件时公开 Props 类型会更方便但不是所有内部类型都应导出。评审重点是公开类型是否稳定、是否从包入口可访问以及声明文件是否与运行时导出一致。any会让错误越过库边界应优先换成明确类型或经过收窄的unknown。1.5 可访问性a11y与语义化标签降级把原生button换成div onClick会丢失键盘、焦点和禁用语义。组件测试要覆盖键盘操作、可访问名称、焦点转移和禁用状态。优先使用原生元素比用多个 ARIA 属性重新模拟更可靠。2. 自动门禁要与发布验证配合静态规则适合发现顶层表达式、危险类型和全局选择器。API 兼容性可以比较声明文件包体与导出则通过真实消费者工程构建。Story 或浏览器测试负责主题、弹层、焦点和交互。每层检查解决不同问题不需要把所有判断塞进一个 AST 脚本。3. Breaking Change 与 Side-Effects AST 自动化检测工具实现下面的 TypeScript Compiler API 示例展示了两种启发式规则扫描顶层表达式以及检查大写函数是否有同名 Props 导出。import * as ts from typescript; export interface CRAuditViolation { filePath: string; line: number; ruleId: SIDE_EFFECT_DETECTED | MISSING_TYPE_EXPORT; message: string; } export function auditComponentSource(filePath: string, fileContent: string): CRAuditViolation[] { const violations: CRAuditViolation[] []; const sourceFile ts.createSourceFile( filePath, fileContent, ts.ScriptTarget.Latest, true ); function visit(node: ts.Node) { // 检查 1: 扫描模块顶层的执行表达式 (Top-level CallExpression) if (ts.isSourceFile(node.parent) ts.isExpressionStatement(node)) { violations.push({ filePath, line: sourceFile.getLineAndCharacterOfPosition(node.getStart()).line 1, ruleId: SIDE_EFFECT_DETECTED, message: 检测到文件顶层存在直接调用的函数/表达式语句这会破坏 Tree-shaking 优化, }); } // 检查 2: 检查导出的 Component 是否缺乏对应的 Props 类型导出 if (ts.isFunctionDeclaration(node) node.modifiers?.some(m m.kind ts.SyntaxKind.ExportKeyword)) { const funcName node.name?.text || ; if (funcName /^[A-Z]/.test(funcName)) { // 首字母大写判断为 UI 组件 const hasPropsExport sourceFile.statements.some((stmt) { if (ts.isTypeAliasDeclaration(stmt) || ts.isInterfaceDeclaration(stmt)) { return stmt.modifiers?.some(m m.kind ts.SyntaxKind.ExportKeyword) stmt.name.text ${funcName}Props; } return false; }); if (!hasPropsExport) { violations.push({ filePath, line: sourceFile.getLineAndCharacterOfPosition(node.getStart()).line 1, ruleId: MISSING_TYPE_EXPORT, message: 组件 ${funcName} 已导出但未显式导出 ${funcName}Props 类型声明, }); } } } ts.forEachChild(node, visit); } visit(sourceFile); return violations; }4. 核对示例规则的误报与漏报示例会把源码顶层的任何表达式语句都判为副作用字符串指令或经过确认的注册入口也会命中真正的副作用还可能藏在变量初始化、装饰器或导入模块中。规则名称写着“检测副作用”实际只能提示“存在顶层表达式”CI 信息应如实描述交给评审者判断。用首字母大写推断组件、再要求${funcName}Props同样属于团队命名约定不是 TypeScript 的通用规则。forwardRef、变量形式组件、默认导出和复合组件都可能漏掉。若项目确实采用这套约定可以把它写进贡献指南并准备正反样例否则更适合检查公开入口的声明产物。门禁效果用真实记录评估某条规则命中后有多少被修复、多少是例外、多少是误报消费者构建是否发现兼容问题。没有项目数据时不编造事故下降或评审提速比例。5. 架构师组件库 CR 审核打卡清单最终批准前按改动范围核对包导出构建后检查main、module、types与exports指向的文件确实存在ESM、CJS 与类型解析符合支持范围。样式影响检查新增全局选择器、Token 默认值、主题嵌套和弹层挂载不用正则前缀替代完整的 CSS 验证。DOM 契约只有消费者确实需要底层节点时才公开 Ref一旦公开就验证类型、焦点和版本兼容。行为测试覆盖渲染、交互、键盘、受控与非受控状态。Snapshot 用来发现结构变化不能代替语义断言。文档与迁移Story 和文档同步更新存在破坏性变更时提供版本说明与迁移方法。设计系统 CR 的核心是守住公共承诺。代码风格可以由工具统一类型、样式、交互和产物是否仍能被旧消费者正确使用才需要评审者逐项确认。
返回列表