ARTICLE DETAIL

资讯详情

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

Storybook 多框架 Props 类型声明与 JSDoc 文档注释实践:让 Controls 和 Docs 自动生成

Storybook 多框架 Props 类型声明与 JSDoc 文档注释实践:让 Controls 和 Docs 自动生成 Storybook 多框架 Props 类型声明与 JSDoc 文档注释实践让 Controls 和 Docs 自动生成【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook在 Storybook 中一个 Story 打开后 Controls 面板里的开关与输入框、Docs 页 ArgsTable 中的类型和描述列多数时候并不来自.stories文件而是来自组件源码里的 Props 类型声明与 JSDoc 注释。本篇以 docgen 构建后的产物 argTypes 为起点逐字段倒推回 Svelte、Vue 3、React、Angular、Web ComponentsLit五个框架各自该写什么代码、写在什么位置最后给出一个可直接落地的对照表与首个 Story 的验证清单。文中的组件代码取自 Storybook 仓库官方教程片段 docs/_snippets/button-component-with-proptypes.md统一为「一个布尔禁用开关 一段展示文本」的最小 Button。先看 docgen 跑完后的产物一个 argTypes 对象meta 中写入component: Button之后各框架预设会在构建期启动 docgen 读取组件源码把每个属性推导成一份元数据。官方教程中给出的样例形态如下见 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.name给出string、boolean这类类型名直接决定 Controls 渲染文本框还是开关description来自贴在属性上的 JSDoc 注释最终出现在 ArgsTable 的描述列与 Controls 的悬浮提示中defaultValue与table.defaultValue.summary来自代码中的初值分别驱动「初始值」与表格的 Default 列control可由 argTypes 手动覆盖未覆盖时按type自动推导。仓库中各框架的解析入口均为真实路径框架解析工具源码位置ReactVitereact-docgenjoshwooding/vite-plugin-react-docgen-typescriptcode/frameworks/react-vite/package.json、code/frameworks/react-vite/src/plugins/react-docgen.tsReactWebpackstorybook/react-docgen-typescript-plugincode/presets/react-webpack/package.jsonAngularstorybook/angular-compodocCompodoccode/frameworks/angular/package.json、code/frameworks/angular/build-schema.json 中的compodoc/compodocArgsVue 3vue-docgen-api 内部 docgen workercode/renderers/vue3/package.json、code/renderers/vue3/src/docgen/Sveltesveltedoc-parser驱动的 Vite 插件code/frameworks/svelte-vite/src/plugins/svelte-docgen.tsLit直接解析类 JSDocprop/summary与装饰器code/renderers/web-components/src/docs/custom-elements.ts下面按「声明写在语法树哪个节点」的顺序从声明最显眼的 Svelte 讲起。Svelteexport let是 docgen 唯一关注的声明位Svelte 组件没有单独的 props 选项props 就是script中export let出来的变量。Vite 框架的 docgen 插件code/frameworks/svelte-vite/src/plugins/svelte-docgen.ts用sveltedoc-parser读取注释映射规则是变量名 → 属性名JSDoc 正文 →descriptionrequired标签 → 必填语义右侧的字面量 →defaultValue。script /** * A Button Component * component */ /** * Disable the button * required */ export let disabled false; /** * Button content * required */ export let content ; /script button typebutton {disabled}{content}/button模板里{disabled}是disabled{disabled}的简写。注意官方教程片段中此处写作script/——这是未闭合的脚本标签Svelte 编译器会直接报错落到项目里必须改成/script本例已按正确写法给出。Vue 3props选项的三要素决定整行元数据Options API 中type、default、required三个键一一对应 argTypes 的type.name、table.defaultValue、required字段上方的注释则进入description。template button typebutton :disabledisDisabled{{ 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, }, }, }; /scriptTypeScript 版本把选项对象包进defineComponentsetup(props)里的props随之获得完整类型推导而 docgen 读取的结构不变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, }, }, }); /script两点值得注意其一官方 JS 片段在isDisabled上同时写了required: true和default: false而 TS 片段只保留default——「必填且带默认值」的并存写法在 Vue 里语义自相矛盾required: true会让缺省传入直接告警default又暗示可缺省二选一即可其二name: button是单词组件名会被vue/multi-word-component-names规则警告真实项目建议改成my-button这类多词名。另外 Vue 示例的文案属性叫label而其余框架叫content属于官方片段间的命名漂移跨框架抄写时先统一属性名。ReactPropTypes 与 interface 是两条独立的解析路径JavaScript 版本把类型元信息挂在运行时的propTypes上react-docgen逐字段读取isRequired写入requiredJSDoc 写入descriptionimport 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, };TypeScript 版本不依赖运行时声明react-docgen-typescript静态读取 interfaceReact.FCButtonProps的泛型让 Story 里的args也受检查export 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 ); };这里有个容易忽视的矛盾interface 字段没有?即静态上必填而解构默认值 false/ 又会在 argTypes 里贡献 defaultValue——「必填」与「有默认值」并存Controls 面板会同时显示开关的初始状态和「必填」标记。要让属性真正可省略给字段加?才是决定性的一步解构默认值并不改变required字段。AngularInput()字段即对外 PropsCompodoc 解析组件类时只把带Input()的字段视为对外属性字段的 TS 类型进type.name字段上方的 JSDoc 进description若字段赋了初值则进defaultValueimport { 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 标签撞名。官方片段未给字段初值因此 docgen 推导不出默认值ArgsTable 的 Default 列会留空若希望 Controls 显示初始状态直接写isDisabled false即可。LitWeb Components类级 JSDoc 与装饰器分工Lit 场景下可观察属性有两个声明位——JS 版的static get properties()与 TS 版的property()装饰器——它们共同决定「哪些成员是 props、类型是什么」而每个 prop 的描述并不写在成员旁而是集中在类上方的 JSDoc 块里以prop逐条列出summary与tag则分别对应组件级描述和元素名。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);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 typebutton ?disabled${this.isDisabled}${this.content}/button ; } }customElement(custom-button)替代了手写的customElements.define?加字段初值表达「可省略但有默认值」。?disabled${...}是 Lit 模板的布尔属性绑定语法对应 HTML 属性存在即启用。五个框架声明方式对照与不一致点框架版本声明位置docgen 实际读取的内容默认值写法必填如何表达ReactJSButton.propTypes静态属性PropTypes.xxx与 JSDoc无片段未提供isRequiredReactTSButtonPropsinterface字段类型与 JSDoc解构默认值 false / 字段省略?Angular类字段 Input()字段类型、JSDoc、初值字段初值片段未写JSDocrequiredVue 3JS/TSprops选项type/default/required与注释default: false / Onerequired: trueSveltescript内export let变量 JSDoc 与初值 false / JSDocrequiredLitJSstatic get properties()类级propJSDoc构造函数赋值无显式语法靠默认值约定LitTSproperty()字段类级propJSDoc字段初值 One / false同上不一致点清单跨框架抄写片段前建议逐条过一遍同一语义属性名各叫各的React/Angular/Lit 用isDisabledSvelte 片段用disabledVue 片段的文案属性又叫label而非content「必填 默认值」并存React TS 的isRequired/必填 interface 与解构默认值并存Vue JS 片段required: true与default并存docgen 会把两者都如实写进 argTypes面板信息因此显得矛盾必填的表达机制完全不同运行时期数isRequired、required、静态类型?、JSDoc 标签required各占一派没有跨框架的统一语义Svelte 官方片段存在script/未闭合写法直接复制会编译失败需改为/scriptweb-components 的 meta 与其余框架不同component传的是标签名字符串如demo-button见 docs/_snippets/button-story-matching-argtypes.md其余框架传组件类本身。组件声明完成后写第一个 Story 并验证声明就位后.stories文件里只需一个最小 metaReact 为例import type { Meta } from storybook/react-vite; import { Button } from ./Button; const meta { component: Button, parameters: { actions: { argTypesRegex: ^on.* } }, } satisfies Metatypeof Button; export default meta;component: Button是 docgen 与 Story 之间的挂钩——缺了它argTypes 推导不会针对该 Story 生效argTypesRegex: ^on.*让以on开头的属性自动接入 Actions 面板本例没有事件属性属于通用写法。Vue 写法相同改为import Button from ./Button.vue后把类传入即可Svelte 则用storybook/addon-svelte-csf的defineMeta({ component: Button, ... })。启动storybook dev后按这份清单逐项确认Controls 形态isDisabled显示为开关、content显示为文本框——验证type.name推导正确初始值开关处于 off、文本框内已有初值若声明了默认值——验证defaultValue链路联动拨动开关预览中按钮应同步禁用——验证 props 真的接到了组件ArgsTableDocs 页的描述列出现 JSDoc 文案、Default 列出现初值——验证description/table.defaultValueControls 悬浮提示与注释原文一致。若描述缺失先检查 JSDoc 是否贴在「属性本身」上——interface 字段、Input()字段、export let变量、props选项项各自框架的解析器只认这些固定位置写在别处的注释不会被拾取。写准类型、写全注释Controls 与 Docs 的自动生成随之成立这正是把 Props 声明当作文档工程而非格式要求的原因。官方教程后续从组件走向 Story 的完整链路可继续阅读 docs/get-started/whats-a-story.mdx、docs/writing-stories/args.mdx 与 docs/essentials/actions.mdx。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表