AI 驱动的组件 API 设计建议:基于使用频率与命名一致性的智能推荐
AI 驱动的组件 API 设计建议基于使用频率与命名一致性的智能推荐一、当 Table 组件有 47 个 Props——API 膨胀的无声灾难去年重构公司的通用表格组件时我打开 Props 定义文件看到 47 个属性。onRowClick、onRowSelect、onClickRow、handleRowClick——一个点击行的行为有四种命名方式分别是四位工程师在不同年份留下的遗产。这不是个例。组件库 API 的熵增是天然趋势每个新需求都可能引入一个新 Prop每个新加入的工程师都可能带入自己的命名习惯。AI 在这里的价值不是告诉你这个组件该怎么设计而是基于全量代码仓库的使用数据给你一份量化报告哪些 Props 承担了 90% 的使用哪些是僵尸属性定义了但从未被使用命名不一致的 Props 集群onClickvshandleClickvsclickHandler应该拆分但被强行合并的 Props一个options对象里藏了 15 个语义独立的配置项二、API 分析引擎的架构设计系统的核心是Props 使用频率矩阵——一张二维表行是组件名列是 Props 名值是该 Props 在该组件中被使用的次数。这张表是后续所有分析僵尸检测、聚类、组合模式挖掘的基础。三、生产级实现// analyzer/props-analyzer.ts // 组件 API 分析引擎 // 遍历项目中所有 tsx/jsx 文件提取组件使用模式 import * as ts from typescript; import * as fs from fs; import * as path from path; import { globSync } from glob; interface PropUsage { componentName: string; propName: string; usageCount: number; // 该 Prop 的使用示例代码片段 examples: string[]; // 该 Prop 的值类型分布{ string: 120, boolean: 45, enum: 30 } valueTypes: Recordstring, number; } interface APIReport { // 僵尸属性定义了但从未被使用的 Props zombieProps: Array{ component: string; prop: string; definedIn: string }; // 命名不一致集群功能相同但命名不同的 Props 组 inconsistentNaming: Array{ pattern: string; // 语义模式如 行点击事件 variants: Array{ component: string; prop: string; count: number }; recommendation: string; // AI 推荐的标准命名 }; // 高内聚 Props 组应该拆分为子组件的 Props 集合 cohesiveGroups: Array{ component: string; props: string[]; // 总是一起出现的 Props 组 cooccurrenceRate: number; // 共现率0~1 suggestion: string; }; } /** * 主分析入口 * param projectRoot 项目根目录递归搜索所有 .tsx 文件 */ function analyzeComponentAPI(projectRoot: string): APIReport { const tsxFiles globSync(${projectRoot}/**/*.tsx, { ignore: [**/node_modules/**, **/dist/**] }); const allUsages: PropUsage[] []; for (const file of tsxFiles) { const source fs.readFileSync(file, utf-8); const sourceFile ts.createSourceFile( file, source, ts.ScriptTarget.Latest, true ); // 遍历 AST收集所有 JSX 元素的 Props 使用 collectJSXProps(sourceFile, source, allUsages); } // 合并同一组件的同一 Prop 的使用数据 const merged mergeUsages(allUsages); return { zombieProps: detectZombieProps(merged), inconsistentNaming: detectNamingInconsistency(merged), cohesiveGroups: detectCohesiveGroups(merged) }; } /** * AST 遍历从 JSX 元素中提取组件名和 Props * 设计意图不仅收集 Props 名称还收集值类型 * 为后续的类型不一致检测提供数据基础 */ function collectJSXProps( node: ts.Node, source: string, usages: PropUsage[] ): void { // JSX 自闭合标签Button typeprimary / // JSX 开闭标签Table data{list}Column title名称 //Table if (ts.isJsxSelfClosingElement(node) || ts.isJsxOpeningElement(node)) { const tagName node.tagName.getText(source); // 跳过原生 HTML 标签div, span, input...只分析自定义组件 if (isNativeHTML(tagName)) return; // 遍历该 JSX 元素的所有属性 node.attributes.properties.forEach((attr) { if (ts.isJsxAttribute(attr)) { const propName attr.name.getText(source); // 推断属性值的类型 const valueType inferValueType(attr.initializer, source); usages.push({ componentName: tagName, propName, usageCount: 1, examples: [getNodeText(attr, source)], valueTypes: { [valueType]: 1 } }); } }); } // 递归遍历子节点 ts.forEachChild(node, (child) collectJSXProps(child, source, usages)); } /** * 检测命名不一致 * 核心思路将 Props 名称按语义模式聚类 * * 例如onRowClick、handleRowClick、rowClickHandler * 都匹配模式 /row.*click|click.*row/i → 它们应该统一命名 */ function detectNamingInconsistency(usages: PropUsage[]) { // 语义模式映射表——将相似命名的 Props 归为同一语义意图 const patterns [ { regex: /(on|handle)?\w*click\w*/i, semantic: 点击事件, convention: on{Target}Click }, { regex: /(on|handle)?\w*change\w*/i, semantic: 变更事件, convention: on{Target}Change }, { regex: /(is|has|should|can)\w/i, semantic: 布尔状态, convention: {prefix}{Noun} }, { regex: /render\w*/i, semantic: 自定义渲染, convention: render{Noun} }, ]; const results []; for (const { regex, semantic, convention } of patterns) { // 找到所有匹配该语义的 Props const matched usages.filter(u regex.test(u.propName)); if (matched.length 1) { // 按组件分组 const byComponent groupBy(matched, componentName); const variants Object.entries(byComponent).map(([comp, props]) ({ component: comp, prop: props[0].propName, count: sum(props, usageCount) })); // 如果同一语义在不同组件中有不同的命名标记为不一致 const uniqueNames new Set(variants.map(v v.prop)); if (uniqueNames.size 1) { results.push({ pattern: semantic, variants, recommendation: convention }); } } } return results; } /** * 检测僵尸属性 * 逻辑很简单在组件定义文件中解析 Props 接口然后检查全量使用数据中是否有匹配 */ function detectZombieProps(usages: PropUsage[]) { // 1. 从组件定义文件中提取所有声明的 Props const definedProps extractAllDefinedProps(); // 2. 找出定义过但从未出现在 usage 中的 Props const usedProps new Set(usages.map(u ${u.componentName}.${u.propName})); return definedProps .filter(dp !usedProps.has(${dp.component}.${dp.prop})) .map(dp ({ component: dp.component, prop: dp.prop, definedIn: dp.sourceFile })); } // 注extractAllDefinedProps 需解析组件源文件的 TypeScript 类型定义篇幅所限略去实现细节四、信任边界与误判控制AI 分析最大的风险是误报——把一个正常使用的 Props 标记为僵尸属性或者把一个有意的命名差异例如onApply和onConfirm确实是不同语义标记为不一致。降低误报的三条规则必须有足够样本量。组件使用少于 5 次的不参与任何分析统计噪声太大。语义聚类必须有人工 Review 环节。onRowClick和onCellClick虽然都匹配 click 模式但它们是不同的语义意图。AI 只能提疑似不一致最终判定权留给组件 Owner。僵尸属性不等同于立刻删除。它可能被外部的消费方不在当前仓库中使用。处理策略应该是标记 deprecated观察一个版本周期。五、总结组件 API 的设计质量不是一次 Code Review 能解决的它是一个持续熵增过程。AI 的价值是把这种缓慢退化的趋势量化、可视化、可干预。三个核心输出使用频率矩阵——告诉组件维护者你的用户在怎么用你的 API命名一致性报告——把分散在不同组件中的命名分歧聚合到一张表里共现关系图——揭示 Props 之间的隐式依赖关系为拆分/合并决策提供数据依据工具的本质是把我觉得变成数据显示。当 47 个 Props 中只有 11 个承担了 95% 的使用量重构的优先级自然就清楚了。