当组件文档不再是手写苦役AI 辅助从 Props 到 Storybook 用例的自动生成一、深度引言与场景痛点组件文档是设计系统的说明书——每个组件的 Props 类型、默认值、交互状态、使用场景都需要详尽记录让新成员不看源码就能正确使用组件。但写文档是前端开发中最枯燥的苦役——一个 Button 组件有 7 个 Props、4 个交互状态、3 种变体文档需要覆盖所有组合场景手写工作量是编码工作量的 2-3 倍。Storybook 是组件文档的主流载体——每个 Story 是一个组件的交互用例展示一种特定的 Props 组合和状态配置。但 Story 的编写也是手活——每个 Story 需要手动定义 Props 值、手动配置交互模拟、手动标注用例描述。一个有 10 个 Props 的组件完整覆盖所有组合需要 20-30 个 Story每个 Story 5-10 行代码总编写量 100-300 行。AI 辅助文档生成的介入点从组件的 TypeScript Props 定义出发自动推导出 Story 的配置——每个 Props 的合法值列表、状态组合的覆盖矩阵、关键交互用例的描述。AI 把文档编写从手活变成推导 确认的流程——推导覆盖所有必须场景确认确保描述准确。二、底层机制与原理深度剖析AI 辅助文档生成的核心机制是Props 定义 → Story 矩阵 → 用例描述三段式推导。从 TypeScript Props 类型定义出发提取每个 Props 的类型信息和默认值生成覆盖所有状态的 Story 矩阵再用 AI 为每个 Story 生成语义化的用例描述。flowchart TD A[Button.tsx Props 类型定义] -- B[Props 解析层] B -- B1[提取每个 Prop 的类型string / enum / boolean] B -- B2[提取每个 Prop 的默认值] B -- B3[提取枚举类型的合法值列表] B1 B2 B3 -- C[Story 矩阵生成层] C -- C1[核心 Story默认配置 每个 Prop 的关键值] C -- C2[状态 Storyidle / loading / success / error / disabled] C -- C3[变体 Storyprimary / secondary / danger] C1 C2 C3 -- D[用例描述生成层] D -- D1[AI 为每个 Story 生成描述文本] D -- D2[标注交互行为用户可以做什么] D -- D3[标注无障碍属性ARIA 标签和键盘行为] D1 D2 D3 -- E[Storybook 配置输出] E -- E1[stories.tsx 文件所有 Story 定义] E -- E2[MDX 文档页组件使用说明] E -- E3[交互测试配置play function 定义]Props 解析层提取类型定义中的结构化信息。TypeScript 的 Props 接口包含每个属性的类型string | number | boolean | enum、默认值通过 JSDocdefault标注或组件代码中的 defaultProps、枚举值列表联合类型primary | secondary | danger。解析这些信息后每个 Prop 的合法值范围就确定了——这是 Story 矩阵生成的基础。Story 矩阵生成层根据 Props 的合法值范围计算需要覆盖的 Story 组合。矩阵生成的原则是最小覆盖 关键组合——不是穷举所有 Prop 组合组合爆炸而是只覆盖每个 Prop 的关键值变化和最重要的状态组合。比如 Button 组件的 7 个 Props穷举组合有上千种但最小覆盖只需要 10-15 个 Story——默认配置、每个变体类型、每个交互状态、每个边界值空值、最大长度。用例描述生成层让每个 Story 不仅是代码还有语义化的文字说明。AI 为每个 Story 生成一段 1-2 句的描述说明这个用例展示的场景和交互行为。描述不是泛泛的这是一个按钮而是这是主色变体的禁用状态按钮显示灰色外观且不可点击适用于表单未填写完整时的提交按钮场景。三、生产级代码实现与最佳实践Props 解析脚本// scripts/storybook-generator/props-parser.ts import { parse } from babel/parser; import { traverse } from babel/traverse; import fs from fs; interface PropInfo { name: string; type: string | number | boolean | enum | function | object; defaultValue?: string; enumValues?: string[]; // 枚举类型的合法值列表 required: boolean; description?: string; // JSDoc 描述 } // 解析组件的 Props TypeScript 类型定义 function parsePropsFromComponent(filePath: string): PropInfo[] { const code fs.readFileSync(filePath, utf-8); const ast parse(code, { sourceType: module, plugins: [typescript, jsx] }); const props: PropInfo[] []; traverse(ast, { // 查找 interface 或 type 定义 TSTypeAnnotation(path) { const typeNode path.node.typeAnnotation; if (typeNode.type TSTypeLiteral) { for (const member of typeNode.members) { if (member.type TSPropertySignature) { const propName member.key.name || member.key.value; const propType extractPropType(member.typeAnnotation?.typeAnnotation); const isRequired !member.optional; props.push({ name: propName, type: propType.type, defaultValue: extractDefaultFromJSDoc(member), enumValues: propType.enumValues, required: isRequired, description: extractDescriptionFromJSDoc(member), }); } } } } }); return props; } // 提取 Prop 的类型信息 function extractPropType(typeNode: any): { type: string; enumValues?: string[] } { if (!typeNode) return { type: any }; // 联合类型提取枚举值列表 if (typeNode.type TSUnionType) { const values typeNode.types .filter(t t.type TSLiteralType) .map(t t.literal.value); if (values.length 0) { return { type: enum, enumValues: values }; } } // 基本类型映射 const typeMap: Recordstring, string { TSStringKeyword: string, TSNumberKeyword: number, TSBooleanKeyword: boolean, TSFunctionType: function, }; return { type: typeMap[typeNode.type] || any }; } // 从 JSDoc 注释中提取默认值 function extractDefaultFromJSDoc(member: any): string | undefined { // 简化实现查找 default 标注 return undefined; } function extractDescriptionFromJSDoc(member: any): string | undefined { return undefined; }Story 矩阵生成// scripts/storybook-generator/story-matrix.ts interface StoryConfig { name: string; // Story 名称如 Primary Disabled props: Recordstring, any; // Props 配置值 description: string; // 用例描述 category: default | variant | state | edge; } function generateStoryMatrix(props: PropInfo[]): StoryConfig[] { const stories: StoryConfig[] []; // 1. 默认 Story所有 Props 使用默认值 const defaultProps: Recordstring, any {}; for (const prop of props) { if (prop.defaultValue) { defaultProps[prop.name] parseValue(prop.defaultValue, prop.type); } else if (prop.type boolean) { defaultProps[prop.name] false; } else if (prop.type string) { defaultProps[prop.name] 示例文本; } } stories.push({ name: Default, props: defaultProps, description: 默认配置下的按钮展示基础外观和交互行为, category: default }); // 2. 变体 Story枚举类型的每个合法值 const variantProp props.find(p p.enumValues p.name.includes(variant)); if (variantProp?.enumValues) { for (const value of variantProp.enumValues) { stories.push({ name: ${capitalize(value)} Variant, props: { ...defaultProps, [variantProp.name]: value }, description: ${value} 变体按钮适用于${describeVariant(value)}场景, category: variant }); } } // 3. 状态 Story交互状态的每个关键状态 const stateStories [ { name: Loading, props: { ...defaultProps, loading: true }, description: 加载状态按钮显示旋转图标且禁止点击用于异步操作等待期 }, { name: Disabled, props: { ...defaultProps, disabled: true }, description: 禁用状态按钮灰暗外观且不可交互用于表单未完整填写时的提交按钮 }, { name: Success, props: { ...defaultProps, state: success }, description: 成功状态按钮短暂闪现确认色用于操作完成后的反馈提示 }, { name: Error, props: { ...defaultProps, state: error }, description: 错误状态按钮显示错误色并允许重试用于操作失败后的恢复路径 }, ]; stories.push(...stateStories.map(s ({ ...s, category: state as const }))); // 4. 边界 Story极端 Props 值 stories.push({ name: Long Text, props: { ...defaultProps, children: 这是一个非常长的按钮文字内容测试按钮在文字溢出时的表现 }, description: 文字内容超长的按钮测试按钮宽度自适应和文字溢出处理, category: edge }); stories.push({ name: Empty Text, props: { ...defaultProps, children: }, description: 文字内容为空的按钮测试按钮最小宽度保持和空内容处理, category: edge }); return stories; } function parseValue(value: string, type: string): any { if (type boolean) return value true; if (type number) return Number(value); return value.replace(//g, ); } function capitalize(s: string): string { return s.charAt(0).toUpperCase() s.slice(1); } function describeVariant(variant: string): string { const map: Recordstring, string { primary: 主要操作, secondary: 辅助操作, danger: 危险或删除操作, }; return map[variant] || variant; }Storybook 配置输出// scripts/storybook-generator/storybook-writer.ts function generateStorybookFile( componentName: string, stories: StoryConfig[], outputPath: string ): void { // 生成 Story 的 import 和 meta 定义 const imports import type { Meta, StoryObj } from storybook/react; import { ${componentName} } from ./${componentName};; const meta const meta: Metatypeof ${componentName} { title: Components/${componentName}, component: ${componentName}, tags: [autodocs], argTypes: { ${stories[0].props ? Object.keys(stories[0].props).map(key ${key}: { control: text }).join(,\n ) : } }, }; export default meta;; // 生成每个 Story 的定义 const storyDefinitions stories.map(story { const storyName story.name.replace(/\s/g, ); const propsStr Object.entries(story.props) .map(([key, value]) ${key}: ${JSON.stringify(value)}) .join(,\n ); return export const ${storyName}: StoryObjtypeof ${componentName} { args: { ${propsStr} }, play: async ({ canvasElement }) { // 交互测试键盘 Tab 聚焦 Enter 触发 const canvas within(canvasElement); const button canvas.getByRole(button); await userEvent.tab(); expect(button).toBeFocused(); }, parameters: { docs: { description: { story: ${story.description} } } }, };; }).join(\n\n); const fullContent ${imports}\n\n${meta}\n\n${storyDefinitions}\n; fs.writeFileSync(outputPath, fullContent); }AI 用例描述增强// scripts/storybook-generator/ai-description.ts import { OpenAI } from openai; async function enhanceDescriptions( stories: StoryConfig[], componentContext: string ): PromiseStoryConfig[] { const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const storySummaries stories.map(s Story ${s.name}: Props${JSON.stringify(s.props)}, 当前描述${s.description} ).join(\n); const response await openai.chat.completions.create({ model: gpt-4o, messages: [ { role: system, content: 你是组件文档写作专家。为每个 Story 用例生成语义化的描述文本包含 1. 用例展示的具体场景 2. 用户的交互行为描述 3. 无障碍注意事项键盘操作、ARIA 属性 描述不超过 2 句话具体且可操作。 }, { role: user, content: 组件上下文${componentContext}\n\nStory 列表\n${storySummaries} } ], response_format: { type: json_object }, max_tokens: 4000 }); const result JSON.parse(response.choices[0].message.content || {}); return stories.map((s, i) ({ ...s, description: result.descriptions?.[i] || s.description })); }四、边界分析与架构权衡Props 解析的 TypeScript 语法覆盖。Babel parser 支持 TypeScript 语法但不覆盖所有高级类型特性——泛型 PropsPropsT、条件类型T extends ... ? ... : ...、映射类型{ [K in keyof T]: ... }。这些高级类型在大型组件库中常见解析器可能无法完整提取类型信息。解决方案对无法自动解析的 Props生成占位 Story 配置标记为需手动补充不跳过而是留出填充位置。Story 矩阵的组合爆炸。一个有 7 个 Props 的组件如果每个 Props 有 3 个合法值穷举组合需要 3^7 2187 个 Story。矩阵生成层的最小覆盖策略将 Story 数量控制在 15-20 个但覆盖不完整——某些 Prop 组合可能产生独特行为比如 disabled loading 同时为 true这些组合不在最小覆盖中。解决方案AI 推导关键组合——识别哪些 Prop 组合可能产生独特行为将这些组合加入 Story 矩阵。AI 描述的准确性与生成成本。每个 Story 的描述增强需要一次大模型调用20 个 Story 需要 20 次调用或一次批量调用。批量调用成本低但描述质量可能不一致模型在长输出中容易遗漏部分 Story逐个调用质量稳定但成本翻倍。权衡核心 Story默认、变体、关键状态使用逐个调用保证质量边界 Story极端值、罕见组合使用批量调用降低成本。Storybook play 函数的交互测试。生成的 play 函数只覆盖了基本的键盘操作测试Tab 聚焦 Enter 触发不覆盖更复杂的交互场景——拖拽、手势、长按、多步骤操作。这些复杂交互的 play 函数需要人工编写因为 AI 无法从 Props 定义推导出复杂交互的精确步骤。自动生成覆盖基本交互复杂交互留给开发者补充。五、总结组件文档的自动生成不是消灭文档编写而是把编写从手活变成推导 确认——从 Props 类型定义推导出 Story 矩阵从 Story 配置推导出用例描述推导覆盖必须场景确认确保描述准确。文档编写的工作量从 2-3 倍编码量缩减到 0.5-1 倍剩余的工作是确认而非创造。推导的关键在于最小覆盖策略——不是穷举所有组合组合爆炸而是覆盖每个 Prop 的关键值和最重要的状态组合。最小覆盖保证文档的实用价值最常见的场景都被覆盖同时控制 Story 数量在可管理范围内15-20 个而非上千个。自动生成的文档是起点而非终点——AI 推导的 Story 配置需要开发者确认 Props 值是否合理AI 生成的描述需要设计师确认场景描述是否准确复杂交互的 play 函数需要开发者手动补充。自动生成减少的是重复劳动保留的是创意决策。让机器做推导让人做判断这才是文档自动化的正确节奏。
当组件文档不再是手写苦役:AI 辅助从 Props 到 Storybook 用例的自动生成
当组件文档不再是手写苦役AI 辅助从 Props 到 Storybook 用例的自动生成一、深度引言与场景痛点组件文档是设计系统的说明书——每个组件的 Props 类型、默认值、交互状态、使用场景都需要详尽记录让新成员不看源码就能正确使用组件。但写文档是前端开发中最枯燥的苦役——一个 Button 组件有 7 个 Props、4 个交互状态、3 种变体文档需要覆盖所有组合场景手写工作量是编码工作量的 2-3 倍。Storybook 是组件文档的主流载体——每个 Story 是一个组件的交互用例展示一种特定的 Props 组合和状态配置。但 Story 的编写也是手活——每个 Story 需要手动定义 Props 值、手动配置交互模拟、手动标注用例描述。一个有 10 个 Props 的组件完整覆盖所有组合需要 20-30 个 Story每个 Story 5-10 行代码总编写量 100-300 行。AI 辅助文档生成的介入点从组件的 TypeScript Props 定义出发自动推导出 Story 的配置——每个 Props 的合法值列表、状态组合的覆盖矩阵、关键交互用例的描述。AI 把文档编写从手活变成推导 确认的流程——推导覆盖所有必须场景确认确保描述准确。二、底层机制与原理深度剖析AI 辅助文档生成的核心机制是Props 定义 → Story 矩阵 → 用例描述三段式推导。从 TypeScript Props 类型定义出发提取每个 Props 的类型信息和默认值生成覆盖所有状态的 Story 矩阵再用 AI 为每个 Story 生成语义化的用例描述。flowchart TD A[Button.tsx Props 类型定义] -- B[Props 解析层] B -- B1[提取每个 Prop 的类型string / enum / boolean] B -- B2[提取每个 Prop 的默认值] B -- B3[提取枚举类型的合法值列表] B1 B2 B3 -- C[Story 矩阵生成层] C -- C1[核心 Story默认配置 每个 Prop 的关键值] C -- C2[状态 Storyidle / loading / success / error / disabled] C -- C3[变体 Storyprimary / secondary / danger] C1 C2 C3 -- D[用例描述生成层] D -- D1[AI 为每个 Story 生成描述文本] D -- D2[标注交互行为用户可以做什么] D -- D3[标注无障碍属性ARIA 标签和键盘行为] D1 D2 D3 -- E[Storybook 配置输出] E -- E1[stories.tsx 文件所有 Story 定义] E -- E2[MDX 文档页组件使用说明] E -- E3[交互测试配置play function 定义]Props 解析层提取类型定义中的结构化信息。TypeScript 的 Props 接口包含每个属性的类型string | number | boolean | enum、默认值通过 JSDocdefault标注或组件代码中的 defaultProps、枚举值列表联合类型primary | secondary | danger。解析这些信息后每个 Prop 的合法值范围就确定了——这是 Story 矩阵生成的基础。Story 矩阵生成层根据 Props 的合法值范围计算需要覆盖的 Story 组合。矩阵生成的原则是最小覆盖 关键组合——不是穷举所有 Prop 组合组合爆炸而是只覆盖每个 Prop 的关键值变化和最重要的状态组合。比如 Button 组件的 7 个 Props穷举组合有上千种但最小覆盖只需要 10-15 个 Story——默认配置、每个变体类型、每个交互状态、每个边界值空值、最大长度。用例描述生成层让每个 Story 不仅是代码还有语义化的文字说明。AI 为每个 Story 生成一段 1-2 句的描述说明这个用例展示的场景和交互行为。描述不是泛泛的这是一个按钮而是这是主色变体的禁用状态按钮显示灰色外观且不可点击适用于表单未填写完整时的提交按钮场景。三、生产级代码实现与最佳实践Props 解析脚本// scripts/storybook-generator/props-parser.ts import { parse } from babel/parser; import { traverse } from babel/traverse; import fs from fs; interface PropInfo { name: string; type: string | number | boolean | enum | function | object; defaultValue?: string; enumValues?: string[]; // 枚举类型的合法值列表 required: boolean; description?: string; // JSDoc 描述 } // 解析组件的 Props TypeScript 类型定义 function parsePropsFromComponent(filePath: string): PropInfo[] { const code fs.readFileSync(filePath, utf-8); const ast parse(code, { sourceType: module, plugins: [typescript, jsx] }); const props: PropInfo[] []; traverse(ast, { // 查找 interface 或 type 定义 TSTypeAnnotation(path) { const typeNode path.node.typeAnnotation; if (typeNode.type TSTypeLiteral) { for (const member of typeNode.members) { if (member.type TSPropertySignature) { const propName member.key.name || member.key.value; const propType extractPropType(member.typeAnnotation?.typeAnnotation); const isRequired !member.optional; props.push({ name: propName, type: propType.type, defaultValue: extractDefaultFromJSDoc(member), enumValues: propType.enumValues, required: isRequired, description: extractDescriptionFromJSDoc(member), }); } } } } }); return props; } // 提取 Prop 的类型信息 function extractPropType(typeNode: any): { type: string; enumValues?: string[] } { if (!typeNode) return { type: any }; // 联合类型提取枚举值列表 if (typeNode.type TSUnionType) { const values typeNode.types .filter(t t.type TSLiteralType) .map(t t.literal.value); if (values.length 0) { return { type: enum, enumValues: values }; } } // 基本类型映射 const typeMap: Recordstring, string { TSStringKeyword: string, TSNumberKeyword: number, TSBooleanKeyword: boolean, TSFunctionType: function, }; return { type: typeMap[typeNode.type] || any }; } // 从 JSDoc 注释中提取默认值 function extractDefaultFromJSDoc(member: any): string | undefined { // 简化实现查找 default 标注 return undefined; } function extractDescriptionFromJSDoc(member: any): string | undefined { return undefined; }Story 矩阵生成// scripts/storybook-generator/story-matrix.ts interface StoryConfig { name: string; // Story 名称如 Primary Disabled props: Recordstring, any; // Props 配置值 description: string; // 用例描述 category: default | variant | state | edge; } function generateStoryMatrix(props: PropInfo[]): StoryConfig[] { const stories: StoryConfig[] []; // 1. 默认 Story所有 Props 使用默认值 const defaultProps: Recordstring, any {}; for (const prop of props) { if (prop.defaultValue) { defaultProps[prop.name] parseValue(prop.defaultValue, prop.type); } else if (prop.type boolean) { defaultProps[prop.name] false; } else if (prop.type string) { defaultProps[prop.name] 示例文本; } } stories.push({ name: Default, props: defaultProps, description: 默认配置下的按钮展示基础外观和交互行为, category: default }); // 2. 变体 Story枚举类型的每个合法值 const variantProp props.find(p p.enumValues p.name.includes(variant)); if (variantProp?.enumValues) { for (const value of variantProp.enumValues) { stories.push({ name: ${capitalize(value)} Variant, props: { ...defaultProps, [variantProp.name]: value }, description: ${value} 变体按钮适用于${describeVariant(value)}场景, category: variant }); } } // 3. 状态 Story交互状态的每个关键状态 const stateStories [ { name: Loading, props: { ...defaultProps, loading: true }, description: 加载状态按钮显示旋转图标且禁止点击用于异步操作等待期 }, { name: Disabled, props: { ...defaultProps, disabled: true }, description: 禁用状态按钮灰暗外观且不可交互用于表单未完整填写时的提交按钮 }, { name: Success, props: { ...defaultProps, state: success }, description: 成功状态按钮短暂闪现确认色用于操作完成后的反馈提示 }, { name: Error, props: { ...defaultProps, state: error }, description: 错误状态按钮显示错误色并允许重试用于操作失败后的恢复路径 }, ]; stories.push(...stateStories.map(s ({ ...s, category: state as const }))); // 4. 边界 Story极端 Props 值 stories.push({ name: Long Text, props: { ...defaultProps, children: 这是一个非常长的按钮文字内容测试按钮在文字溢出时的表现 }, description: 文字内容超长的按钮测试按钮宽度自适应和文字溢出处理, category: edge }); stories.push({ name: Empty Text, props: { ...defaultProps, children: }, description: 文字内容为空的按钮测试按钮最小宽度保持和空内容处理, category: edge }); return stories; } function parseValue(value: string, type: string): any { if (type boolean) return value true; if (type number) return Number(value); return value.replace(//g, ); } function capitalize(s: string): string { return s.charAt(0).toUpperCase() s.slice(1); } function describeVariant(variant: string): string { const map: Recordstring, string { primary: 主要操作, secondary: 辅助操作, danger: 危险或删除操作, }; return map[variant] || variant; }Storybook 配置输出// scripts/storybook-generator/storybook-writer.ts function generateStorybookFile( componentName: string, stories: StoryConfig[], outputPath: string ): void { // 生成 Story 的 import 和 meta 定义 const imports import type { Meta, StoryObj } from storybook/react; import { ${componentName} } from ./${componentName};; const meta const meta: Metatypeof ${componentName} { title: Components/${componentName}, component: ${componentName}, tags: [autodocs], argTypes: { ${stories[0].props ? Object.keys(stories[0].props).map(key ${key}: { control: text }).join(,\n ) : } }, }; export default meta;; // 生成每个 Story 的定义 const storyDefinitions stories.map(story { const storyName story.name.replace(/\s/g, ); const propsStr Object.entries(story.props) .map(([key, value]) ${key}: ${JSON.stringify(value)}) .join(,\n ); return export const ${storyName}: StoryObjtypeof ${componentName} { args: { ${propsStr} }, play: async ({ canvasElement }) { // 交互测试键盘 Tab 聚焦 Enter 触发 const canvas within(canvasElement); const button canvas.getByRole(button); await userEvent.tab(); expect(button).toBeFocused(); }, parameters: { docs: { description: { story: ${story.description} } } }, };; }).join(\n\n); const fullContent ${imports}\n\n${meta}\n\n${storyDefinitions}\n; fs.writeFileSync(outputPath, fullContent); }AI 用例描述增强// scripts/storybook-generator/ai-description.ts import { OpenAI } from openai; async function enhanceDescriptions( stories: StoryConfig[], componentContext: string ): PromiseStoryConfig[] { const openai new OpenAI({ apiKey: process.env.OPENAI_API_KEY }); const storySummaries stories.map(s Story ${s.name}: Props${JSON.stringify(s.props)}, 当前描述${s.description} ).join(\n); const response await openai.chat.completions.create({ model: gpt-4o, messages: [ { role: system, content: 你是组件文档写作专家。为每个 Story 用例生成语义化的描述文本包含 1. 用例展示的具体场景 2. 用户的交互行为描述 3. 无障碍注意事项键盘操作、ARIA 属性 描述不超过 2 句话具体且可操作。 }, { role: user, content: 组件上下文${componentContext}\n\nStory 列表\n${storySummaries} } ], response_format: { type: json_object }, max_tokens: 4000 }); const result JSON.parse(response.choices[0].message.content || {}); return stories.map((s, i) ({ ...s, description: result.descriptions?.[i] || s.description })); }四、边界分析与架构权衡Props 解析的 TypeScript 语法覆盖。Babel parser 支持 TypeScript 语法但不覆盖所有高级类型特性——泛型 PropsPropsT、条件类型T extends ... ? ... : ...、映射类型{ [K in keyof T]: ... }。这些高级类型在大型组件库中常见解析器可能无法完整提取类型信息。解决方案对无法自动解析的 Props生成占位 Story 配置标记为需手动补充不跳过而是留出填充位置。Story 矩阵的组合爆炸。一个有 7 个 Props 的组件如果每个 Props 有 3 个合法值穷举组合需要 3^7 2187 个 Story。矩阵生成层的最小覆盖策略将 Story 数量控制在 15-20 个但覆盖不完整——某些 Prop 组合可能产生独特行为比如 disabled loading 同时为 true这些组合不在最小覆盖中。解决方案AI 推导关键组合——识别哪些 Prop 组合可能产生独特行为将这些组合加入 Story 矩阵。AI 描述的准确性与生成成本。每个 Story 的描述增强需要一次大模型调用20 个 Story 需要 20 次调用或一次批量调用。批量调用成本低但描述质量可能不一致模型在长输出中容易遗漏部分 Story逐个调用质量稳定但成本翻倍。权衡核心 Story默认、变体、关键状态使用逐个调用保证质量边界 Story极端值、罕见组合使用批量调用降低成本。Storybook play 函数的交互测试。生成的 play 函数只覆盖了基本的键盘操作测试Tab 聚焦 Enter 触发不覆盖更复杂的交互场景——拖拽、手势、长按、多步骤操作。这些复杂交互的 play 函数需要人工编写因为 AI 无法从 Props 定义推导出复杂交互的精确步骤。自动生成覆盖基本交互复杂交互留给开发者补充。五、总结组件文档的自动生成不是消灭文档编写而是把编写从手活变成推导 确认——从 Props 类型定义推导出 Story 矩阵从 Story 配置推导出用例描述推导覆盖必须场景确认确保描述准确。文档编写的工作量从 2-3 倍编码量缩减到 0.5-1 倍剩余的工作是确认而非创造。推导的关键在于最小覆盖策略——不是穷举所有组合组合爆炸而是覆盖每个 Prop 的关键值和最重要的状态组合。最小覆盖保证文档的实用价值最常见的场景都被覆盖同时控制 Story 数量在可管理范围内15-20 个而非上千个。自动生成的文档是起点而非终点——AI 推导的 Story 配置需要开发者确认 Props 值是否合理AI 生成的描述需要设计师确认场景描述是否准确复杂交互的 play 函数需要开发者手动补充。自动生成减少的是重复劳动保留的是创意决策。让机器做推导让人做判断这才是文档自动化的正确节奏。