Storybook 跨框架 Props 声明实践:一份自动生成 argTypes 与 Controls 的速查指南
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
为什么改了组件的 props,Controls 面板却不更新、Docs 里的参数描述还是空的?因为在 Storybook 里,component一旦声明,docgen 就会解析你的组件源码,把 Props 类型与 JSDoc 注释转换成argTypes——类型决定控件形态,注释变成面板描述,默认值写进参数表格。这份指南覆盖 React、Angular、Vue、Svelte、Web Components(Lit)五类跨框架组件类型写法,帮你把"Props 声明 + 注释"一次写到位。
先讲透数据流:你的声明如何变成 argTypes
🔧 结论先行:docgen 的输入是组件源码本身,输出是结构化的argTypes。整条链路是:
*.stories.*里声明component,docgen 据此定位组件文件;- 各框架的解析器(React 用 react-docgen、Angular 用 Compodoc、Vue 用
vue-docgen-api、Web Components 解析 JSDoc)把类型、默认值、注释抽出来; - 结果合并成
argTypes,Controls 与 Docs 的 ArgsTable 直接按它渲染。
仓库里 storybook-generated-argtypes.md 片段给出了这段推导的产物长什么样:
const 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.name: 'string'来自你在源码里写的类型(PropTypes.string、TS 字段、type: String、@property);description来自字段上方的 JSDoc 注释;defaultValue/table.defaultValue来自默认值写法(解构初值、default、字段初始值);control.type: 'text'由类型推断——布尔渲染成开关、字符串渲染成文本框,这一步完全不用你手写。
所以"声明即文档":你不需要在 stories 里为每个 prop 手工补argTypes,组件写清楚,面板自己长出来。仓库内各框架的解析链路可作佐证:React 侧 code/frameworks/react-vite 依赖react-docgen,Vue 侧 code/renderers/vue3/src/docgen 用vue-docgen-api转换__docgenInfo,Angular 侧则由 Compodoc 生成元数据。
逐框架实战:声明位置、类型来源、默认值与注释位置
以下统一按四要素拆解:声明位置 / 类型来源 / 默认值写法 / 注释位置。代码均取自 button-component-with-proptypes.md 片段,是最小可运行骨架。
React:propTypes 或 interface 配 JSDoc,补齐运行期与编译期类型
声明位置:Button.propTypes(JS)或ButtonPropsinterface(TS);类型来源:PropTypes.*/ TS 类型;默认值:JS 版无(由调用方提供),TS 版走解构初值;注释位置:字段上方 JSDoc。
import React from 'react'; import PropTypes from 'prop-types'; export function Button({ isDisabled, content }) { return ( <button type="button" 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, };TS 版本把类型与默认值都收进 interface 和解构参数:
export interface ButtonProps { /** * Checks if the button should be disabled */ isDisabled: boolean; /** The display content of the button */ content: string; } export const Button: React.FC<ButtonProps> = ({ isDisabled = false, content = '' }) => { return ( <button type="button" disabled={isDisabled}> {content} </button> ); };要点:
isRequired表达必填,缺失时开发环境控制台告警;- 注释块紧贴字段名,react-docgen 会把它提取为
argTypes.description,直接显示在 ArgsTable 和 Controls 里; - TS 版的解构默认值(
= false/= '')会被 docgen 读成defaultValue,缺省时按钮仍可点击。
Angular:@Input()字段就是 Props,注释写在装饰器上方
声明位置:类中@Input()字段;类型来源:字段的 TS 类型;默认值:字段初值(示例未给);注释位置:字段上方 JSDoc。
import { Component, Input } from '@angular/core'; @Component({ selector: 'my-button', template: ` <button type="button" [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; }要点:
[disabled]="isDisabled"属性绑定布尔开关,{{ content }}插值渲染文本;- 想让"禁用"变成可选且有默认值,给字段赋初值即可,如
isDisabled = false——这是 Angular 里表达"可选 + 默认值"的常规写法; - 属性上方加
@requiredJSDoc 标记可表达必填语义(见 button-implementation.md 的 Angular 版本)。
Vue 3:props 选项里同时声明类型、默认值与必填
声明位置:props对象(JS)或defineComponent({ props })(TS);类型来源:type: Boolean / String;默认值:default;注释位置:prop 项上方。
<template> <button type="button" :disabled="isDisabled">{{ label }}</button> </template> <script> 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, }, }, }; </script>TS 版本用defineComponent包住选项对象,setup(props)即获得完整类型推导,结构不变(lang="ts"脚本块内)。
要点:
- 三要素
type/default/required都会被vue-docgen-api转换进argTypes——必填会体现在type.required,default进入table.defaultValue; - "必填 + 默认值"并存时运行期以默认值兜底,两者选一表达更清晰,避免评审时被追问;
- 注意本片段用
label而非 React 版的content展示文案:同一组件在不同框架片段里字段命名可以不同,跨框架对照时留意。
Svelte:export let即 Props,@required标记补必填语义
声明位置:<script>中export let变量;类型来源:Svelte 编译器 + JSDoc;默认值:变量初始值;注释位置:变量上方 JSDoc。
<script> /** * A Button Component * @component */ /** * Disable the button * @required */ export let disabled = false; /** * Button content * @required */ export let content = ''; </script> <button type="button" {disabled}>{content}</button>要点:
export let disabled = false一行同时声明属性与默认值,模板里{disabled}是disabled={disabled}的简写;@required是约定标记,表达"语义上必须提供",供 Svelte CSF / docgen 工具解析;- 属性名是
disabled而非其他框架的isDisabled——同一语义在不同框架命名并不统一,写文档时以实际字段为准。
Web Components(Lit):类上方 JSDoc +static properties或@property()
声明位置:static get properties()(JS)或@property()字段(TS);类型来源:type: String/Boolean或 TS 类型;默认值:构造函数赋值或字段初值;注释位置:类上方 JSDoc 块,逐属性用@prop。
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 type="button" ?disabled=${this.isDisabled}>${this.content}</button> `; } } customElements.define('custom-button', CustomButton);TS 版把声明压缩进装饰器:
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 type="button" ?disabled=${this.isDisabled}>${this.content}</button> `; } }要点:
- 类上方
@prop描述是 web-components docgen 的唯一输入——漏写它就漏掉参数描述; @tag与customElements.define/@customElement共同确定注册名,stories 里component要用同名字符串引用;?disabled=${...}是 Lit 的布尔属性绑定语法,与 React 的disabled={...}语义相同。
一图速查:六大框架 Props 声明对照表
| 组件名 | 框架 / 版本 | 声明方式 | 类型来源 | 默认值写法 | 必填表达 |
|---|---|---|---|---|---|
Button | React (JS) | Button.propTypes | PropTypes.bool/string | 无,调用方提供 | .isRequired |
Button | React (TS) | ButtonPropsinterface | TS 类型 +React.FC泛型 | 解构初值= false/= '' | 字段不加? |
my-button | Angular | @Input()字段 | 字段 TS 类型 | 字段初值(可选) | JSDoc@required |
button | Vue 3 (JS) | props选项 | type: Boolean/String | default: false / 'One' | required: true |
button | Vue 3 (TS) | defineComponent的props | 运行时type+ TS 推导 | default: false / 'One' | required: true |
Button | Svelte | export let | 编译器 + JSDoc | = false/= '' | JSDoc@required |
custom-button | Web Components (JS) | static get properties() | type: String/Boolean | 构造函数赋值 | 默认值约定 |
custom-button | Web Components (TS) | @property()字段 | Lit 装饰器 + TS 类型 | 字段初值 | 默认值约定 |
共性规律三条:注释位置各家不同,但最终都落在argTypes.description;类型与默认值共同决定控件形态和参数表;必填语义的表达手段各异,但 JSDoc@required是跨框架最通用的"软"方案。
🐛 排查与自查:docgen 没生效时的四条实操
声明写对了却不生效,多数卡在这几处:
- 先确认 meta 里
component指向正确。Vue 的 docgen 会主动校验:检测不到组件时报 "Specify meta.component",追踪不到导入时报 "No component file found"。Web Components 更特殊——component必须是注册后的元素名字符串(如'custom-button'),传类引用无效。 - 核对注释是否贴在"正确的那一行"。React 的注释要写在
propTypes字段上方(写在函数参数解构旁无效);Web Components 的@prop必须整块放在类声明上方而非字段旁;Svelte 的注释紧贴export let。位置错一行,描述就丢失。 - 检查类型与期望控件是否一致。布尔声明成
String、或 Lit 里漏了type: Boolean,Controls 会从开关退化成文本框/复选框行为异常。对照 Controls 文档 里的渲染规则逐一核对。 - 区分"没生成"和"没覆盖"。如果
argTypes缺字段,是声明/docgen 问题;如果字段在但描述或默认值不对,检查是否被 meta 里手写的argTypes覆盖、或片段中required: true与default并存导致语义混淆。Svelte 项目另需确认已接入@storybook/addon-svelte-csf,否则@required等标记无人解析。
自查清单一句话版:component 指对了吗?注释贴对行了吗?类型写对了吗?手写 argTypes 是否覆盖了自动推导?
下一步:在*.stories里声明component,让元数据与 Story 挂钩
组件声明完成后,最小 meta 只需把component指过去,docgen 才会开始工作:
// 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 Meta<typeof Button>; export default meta;parameters: { actions: { argTypesRegex: '^on.*' } }会把onClick这类以on开头的属性自动挂上 Actions 记录,详见 actions.mdx;- 组件的
args如何与上面生成的argTypes配合、参数如何进入每个 Story,见 args.mdx; - 更多跨框架按钮示例都沉淀在 可复用片段目录,如 button-story.md。
一句话收尾:把 Props 类型写准、把 JSDoc 写全,Storybook 的 Controls、Docs 与测试能力随之自动生效——这不是文档洁癖,而是跨框架组件开发里最划算的一步投入。
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考