ARTICLE DETAIL

资讯详情

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

Storybook Button 组件 Props 声明指南:如何在 8 种框架实现里自动生成 argTypes 与 Controls

Storybook Button 组件 Props 声明指南:如何在 8 种框架实现里自动生成 argTypes 与 Controls Storybook Button 组件 Props 声明指南如何在 8 种框架实现里自动生成 argTypes 与 Controls在 Storybook 里写一个 Button 组件想让 Controls 面板自动长出开关和文本框、Docs 面板自动生成 Props 属性表关键不在 Story 文件里而在你声明组件 Props 的那段代码上。这份指南把 ReactJS/TS、Angular、Vue 3JS/TS、Svelte、Web ComponentsLit共 8 种实现的声明写法逐一拆透帮你一次搞懂「组件 Props 声明」和「Storybook 文档自动化」之间的完整数据链路。你可能会遇到这样的场景Story 写得没问题Canvas 里组件也渲染出来了可 Controls 面板空空如也Docs 里的属性表也没描述。原因通常只有一个——Storybook 的 docgen 工具docgen 就是文档生成器的缩写一句话解释扫描你的组件源码把 Props 声明读成结构化数据读不到 Props 元信息。它读不到类型就不知道该给你渲染开关还是文本框读不到注释Docs 属性表里那一列 description 就是空的。所以把 Props 声明写对不是洁癖而是整条自动化链路的开关。下面我们从这条链路本身讲起再逐个框架给出可以直接抄走的声明骨架。官方仓库里本文所有示例都来自 docs/_snippets/ 目录下的可复用片段核心文件是 button-component-with-proptypes.md它定义了一个最简 Button一个布尔开关isDisabled加一段展示文本content。同目录的 button-implementation.md 是同一概念的增强版多了primary、size、onClick等属性适合对照着看 Props 从简到繁的演进。Controls 面板为什么是空的argTypes 的数据链路先说清楚数据源头到底长什么样。你在 Story 的 meta 里写下component: Button之后Storybook 会调用对应框架的 docgen 工具去读组件源码推导出一份叫 argTypes 的结构argTypes 直译参数类型表一句话解释把组件每个参数的名称、类型、默认值、描述记成一张表。它大致长这样完整版本见 docs/_snippets/storybook-generated-argtypes.mdconst argTypes { label: { name: label, type: { name: string, required: false }, defaultValue: Hello, description: demo description, table: { type: { summary: string }, defaultValue: { summary: Hello }, }, control: { type: text, }, }, };人话解读type决定 Control 控件的形态——boolean渲染成开关、string渲染成文本框description就是你写在组件上的 JSDoc 注释table.defaultValue就是组件里给的默认值。换句话说argTypes 里几乎没有凭空出现的字段类型、描述、默认值绝大部分来自你在组件上写的PropTypes、TS interface、Input、props对象或 JSDoc 注释。声明写不准下游全跟着空Control 选错控件、Docs 属性表缺列、描述空白都要回到 Props 声明里修。这张表最终呈现的效果就是 Docs 面板里你看到的 Props 属性表——各框架渲染出来的表格都大同小异分 PROPS / INPUTS / EVENTS / SLOTS 等分组每行一个参数这条推导链路在各框架里的具体实现可以在仓库里找到佐证ReactVite 框架在 code/frameworks/react-vite/package.json 中依赖react-docgen与joshwooding/vite-plugin-react-docgen-typescript并在 code/frameworks/react-vite/src/plugins 下自定义了 docgen 的 handler 与 resolverWebpack 侧则由 code/presets/react-webpack/package.json 引入storybook/react-docgen-typescript-plugin。Angular通过storybook/angular-compodoc见 code/frameworks/angular/package.json执行 Compodoc——Angular 社区的文档生成工具负责把Input和注释转成__docgenInfobuilder 选项在 code/frameworks/angular/build-schema.json 中提供compodoc与compodocArgs配置。Angular-Vite 则内置了自己的 docgen worker见 code/frameworks/angular-vite/package.json 中的./internal/docgen-worker导出。Vue 3渲染器依赖vue-docgen-api经由 code/renderers/vue3/src/docgen/build-docgen.ts 和内部 docgen-worker见 code/renderers/vue3/package.json把组件的__docgenInfo转成 argTypes。Svelte借助storybook/addon-svelte-csf的defineMeta读取组件注释。Web Components / Lit直接解析类上方的 JSDocprop、summary等以及property()装饰器。一句话把组件声明写好约等于免费拿到文档和调试面板。接下来按框架过一遍怎么写。ReactPropTypes 与 TS interface 两条路React 的组件可以是一个纯函数但函数参数本身不携带类型信息——你需要额外补一层声明层JS 和 TS 项目的补法完全不同。JS 项目用 PropTypes 补齐运行期类型import React from react; import PropTypes from prop-types; export function Button({ isDisabled, content }) { return ( button typebutton disabled{isDisabled} {content} /button ); } Button.propTypes { /** Checks if the button should be disabled */ isDisabled: PropTypes.bool.isRequired, /** The display content of the button */ content: PropTypes.string.isRequired, };人话解读isDisabled: PropTypes.bool.isRequired里isRequired表示必填开发环境下调用方漏传或类型传错prop-types会在控制台告警。这是 JS 项目给 Props 加校验的主要手段运行期就生效。注释块必须紧贴在propTypes对象内部、属性名上方书写。react-docgen 只提取写在这里的 JSDoc 作为 argTypes 的description写在组件函数上方的注释是读不到的。这个实现没提供任何默认值所以函数参数解构时不带初值。如果你希望缺省时按钮可点、文案为空可以把参数写成{ isDisabled false, content }——这正是下面 TS 版本采用的策略。TS 项目把类型和文档都交给 interfaceexport interface ButtonProps { /** * Checks if the button should be disabled */ isDisabled: boolean; /** The display content of the button */ content: string; } export const Button: React.FCButtonProps ({ isDisabled false, content }) { return ( button typebutton disabled{isDisabled} {content} /button ); };人话解读TS 项目不再需要运行期的prop-types依赖类型安全由 TS 编译器静态保证react-docgen 则通过react-docgen-typescript插件来读取 interface 字段。React.FCButtonProps给函数组件标注了 Props 泛型。interface 的两个字段都没加?所以默认都是必填想让某个字段比如是否禁用在 args 里变为可选只需给字段加?声明粒度完全由你掌控。解构默认值isDisabled false、content 会被 Storybook 收录为 argTypes 的默认值进而出现在属性表的 Default 列里——这是它和 JS 版本最直观的差异。AngularInput 就是 PropsJSDoc 就是描述Angular 组件对外暴露的 Props 就是类里加了Input()装饰器的字段注释写在字段的 JSDoc 里Compodoc 据此生成__docgenInfoimport { Component, Input } from angular/core; Component({ selector: my-button, template: button typebutton [disabled]isDisabled {{ content }} /button, styleUrls: [./button.css], }) export class ButtonComponent { /** * Checks if the button should be disabled */ Input() isDisabled: boolean; /** The display content of the button */ Input() content: string; }人话解读selector: my-button是组件的标签名。Angular 要求自定义元素至少用两个词带连字符就是为了避免和原生 HTML 标签撞名。模板里[disabled]isDisabled是属性绑定布尔开关{{ content }}是插值渲染文本styleUrls: [./button.css]表示样式文件与组件同目录。和 React 的 JS 示例一样这里两个输入都没给默认值。如果组件需要能独立渲染建议给字段赋初值例如isDisabled false这是 Angular 里表达可选 有默认值的常规写法。另外属性上方若附加requiredJSDoc 标记可对照 button-implementation.md 中的 Angular 示例Compodoc 会把它解释为必填语义。Vue 3props 三要素与 defineComponentVue 的声明集中在props对象里JS 和 TS 两版写法差异不大但各有讲究。JS Options APItype、default、required 三要素template button typebutton :disabledisDisabled{{ label }}/button /template script import { reactive } from vue; export default { name: button, props: { /** * Checks if the button should be disabled */ isDisabled: { type: Boolean, default: false, required: true, }, /** * The display label of the button */ label: { type: String, default: One, required: true, }, }, setup(props) { props reactive(props); return { /** * What will be returned here will available to the component * Functions referenced here will act like methods */ }; // }, }; /script人话解读每一项 prop 由type、default、required三要素描述isDisabled布尔、默认false、必填label字符串、默认One、必填。vue-docgen-api会把这些结构——包括字段上方的注释——转成 argTypes它是 Vue3 渲染器 docgen 的直接输入。setup(props)里的props reactive(props)只是占位示意返回对象是空的注释仅说明在这里返回的成员会暴露给模板、返回的函数行为类似方法本示例并未真正返回任何绑定。注意模板里展示文案用的是label而不是content——同一个 Button 在不同片段里字段命名并不统一跨框架对照阅读时要留意。另外name: button是单字组件名真实项目通常会被vue/multi-word-component-names这条 ESLint 规则告警button-implementation.md 里演示了通过注释禁用的做法。TS 版defineComponent 带来完整类型推导template button typebutton :disabledisDisabled{{ label }}/button /template script langts import { defineComponent } from vue; export default defineComponent({ name: button, props: { /** * Checks if the button should be disabled */ isDisabled: { type: Boolean, default: false, }, /** * The display label of the button */ label: { type: String, default: One, required: true, }, }, setup(props) { /** * What will be returned here will available to the component * Functions referenced here will act like methods */ }, }); /script人话解读defineComponent包住选项对象后props的类型会被 TS 推导setup(props)里的props也具备完整类型提示——这是它相对 JS 版的核心增益。对比上一版isDisabled去掉了required: true、只留default: false说明运行时它是可省略的。这也暴露了一个事实必填 默认值并存的写法在不同版本的官方示例之间并不完全一致实际项目里二者约定取其一即可团队内统一最重要。setup这里不再做reactive包装仅保留注释说明返回值的用途。Svelteexport let 就是 Props 本身Svelte 里没有单独的 props 声明区script里的export let变量就是组件的 Propsscript /** * A Button Component * component */ /** * Disable the button * required */ export let disabled false; /** * Button content * required */ export let content ; script/ button typebutton {disabled}{content}/button人话解读export let disabled false一句同时完成了三件事声明布尔属性、标记对外导出、给出默认值。模板里的{disabled}是disabled{disabled}的简写。required标记表达该属性在语义上必须提供配合component这类标签供 Svelte CSF / docgen 工具生成结构化信息。⚠️ 两个坑要留意一是属性名这里是disabled不是其他框架示例里的isDisabled同一语义的命名跨框架并不统一二是片段第 90 行写作script/在真实项目里脚本标签必须闭合为/script否则 Svelte 编译器会直接报错复制进项目前记得修正。Web ComponentsLitJSDoc prop 属性声明Web Components 场景下Storybook 官方示例基于 Lit 实现JS 和 TS 两种写法都要在类上方挂一段 JSDoc 元信息块JS 版static get properties()import { LitElement, html } from lit; /** * prop {string} content - The display label of the button * prop {boolean} isDisabled - Checks if the button should be disabled * summary This is a custom button element * tag custom-button */ export class CustomButton extends LitElement { static get properties() { return { content: { type: String }, isDisabled: { type: Boolean }, }; } constructor() { super(); this.content One; this.isDisabled false; } render() { return html button typebutton ?disabled${this.isDisabled}${this.content}/button ; } } customElements.define(custom-button, CustomButton);人话解读static get properties()声明了自定义元素的可观察属性其中的type决定属性反射与类型转换规则render里?disabled${this.isDisabled}用的是布尔属性绑定语法属性存在即为 true。默认值在构造函数里赋content One、isDisabled false。tag custom-button与customElements.define(custom-button, ...)共同确定元素的注册名。写 Story 时web-components 的 meta 里要用字符串组件名例如component: demo-button见 button-story-matching-argtypes.md而类上方 JSDoc 里的prop描述正是其 argTypes 的来源。TS 版property() 装饰器压缩声明import { LitElement, html } from lit; import { customElement, property } from lit/decorators.js; /** * prop {string} content - The display label of the button * prop {boolean} isDisabled - Checks if the button should be disabled * summary This is a custom button element * tag custom-button */ customElement(custom-button) export class CustomButton extends LitElement { property() content?: string One; property() isDisabled?: boolean false; render() { return html button typebutton ?disabled${this.isDisabled}${this.content}/button ; } }人话解读customElement(custom-button)一次完成类装饰与元素注册不再需要手写customElements.define。property()把字段声明为可响应属性字段上的可选标记?配合默认值表达非必填但有默认值。类上方的 JSDoc 块在两个版本中保持一致docgen 能从 TS 装饰器语法中解析出同样的prop信息所以换 TS 不需要重写注释。一图对照8 种实现声明在哪、类型哪来、必填怎么表达八段代码看完用一张表把关键差异钉死。相比声明位置更值得你记的是必填语义的表达方式和各自的常见坑框架版本Props 声明位置类型从哪来默认值约定必填如何表达常见坑ReactJSButton.propTypesPropTypes.bool/PropTypes.stringisRequired无由调用方传入isRequired注释必须写在propTypes对象内部写外面读不到ReactTSButtonPropsinterfaceTS 类型 React.FC泛型解构默认值 false/ 字段不加?即必填想让某字段可选给 interface 字段加?Angular类中Input()字段字段的 TS 类型字段初值如isDisabled falserequiredJSDoc 标记selector必须双词避免与原生标签冲突Vue 3JSprops选项type: Boolean/Stringrequireddefault: false/Onerequired: truename: button单字名触发 ESLint 多词规则告警Vue 3TSdefineComponent的props运行时type TS 推导default: false/Onerequired: true仅label必填 默认值并存团队约定取其一Sveltescript中export letSvelte 编译器 required false/ requiredJSDoc 标记属性名disabled与其他框架不统一示例的script/需改/scriptWeb ComponentsJSstatic get properties()type: Boolean/String构造函数赋值主要依赖默认值约定prop、tag必须写在类级 JSDoc 中Web ComponentsTSproperty()字段Lit 装饰器 TS 类型字段初值可选标记? 默认值meta 中component是字符串如demo-button不是类引用抽出三条共性规律跨框架通用每个框架都有注释即文档的约定——propTypes内部、interface 字段上方、Input上方、类级prop这些注释最终都会进入 Docs 属性表成为description一列。类型与默认值共同驱动 Controls——布尔类型自动渲染为开关、字符串渲染为文本框默认值进入属性表的 Default 汇总列。必填语义的表达各不相同——React 用isRequiredVue 用required: trueAngular / Svelte 用required标记Web Components 主要靠默认值约定。切框架时先确认这一条别生搬硬套。各框架 Docs 页最终呈现的形态大同小异组件预览、描述、下方自动生成的属性表一眼能看清所有参数从声明到文档写出第一个 Story接通整条链路声明写完之后最后一步是在.stories文件里用 CSFComponent Story Format即组件故事格式.stories文件的标准写法把组件挂到 Storybook 上。以 React 为例的最小 meta// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import type { Meta } from storybook/your-framework; import { Button } from ./Button; const meta { component: Button, parameters: { actions: { argTypesRegex: ^on.* } }, } satisfies Metatypeof Button; export default meta;人话解读component: Button是触发 docgen 的那一行——前面各框架里写的所有 Props 声明和注释只有在这里声明后才真正被消费argTypes 推导从此启动。如果组件带onClick之类的事件属性parameters: { actions: { argTypesRegex: ^on.* } }会把以on开头的属性自动映射到 Actions 面板做调用记录原理参见 docs/essentials/actions.mdx。更贴合本组件isDisabled/content的 Story 写法可参考 button-story.md 与 args.mdx 页面跨框架 meta 的完整对照版见 button-story-matching-argtypes.md。链路接通后Controls 面板会按你声明的类型自动长出对应控件改值即时预览回头看整条链路Props 类型写准Control 控件才选得对JSDoc 注释写全Docs 属性表的描述列才不空默认值给到位属性表的 Default 列才有数据。这段组件实现看似只是业务代码实则是 Storybook 组件工作台构建、文档化、测试能否开箱即用的地基——声明写准文档与调试能力随之自动生效。这是跨框架写作 UI 组件时最值得先统一的工程习惯。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表