在 Storybook 中使用 TypeScript 编写类型安全的 Stories:Meta 与 StoryObj 实战指南
在 Storybook 中使用 TypeScript 编写类型安全的 StoriesMeta 与 StoryObj 实战指南【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook在 Storybook 中用 TypeScript 编写 stories可以让你不必在组件文件与故事文件之间来回跳转查找 props编辑器会直接提示缺失的必填 props并为 prop 值提供自动补全体验与在应用代码中直接使用组件完全一致同时 Storybook 会根据这些组件类型自动推断并生成 Controls 参数表格。本文以 Storybook 官方文档 typescript.mdx 为核心结合本仓库Storybook 主仓库code/目录即其源码中各框架Meta/StoryObj类型的真实实现系统讲解如何用Meta与StoryObj两个工具类型为 CSF 文件建立完整类型安全体系并深入剖析satisfies操作符带来的增强校验读完你可以在自己的项目中直接套用并理解其底层机制。CSF 文件的两类类型标注对象在深入代码之前先明确一个核心概念一个 CSF 文件Component Story Format基于 ES6 模块的开放标准由两部分组成它们也是两个需要类型标注的对象component meta组件元数据即 CSF 文件的default export描述并配置组件及其所有 stories包括component、title、decorators、parameters 等字段。stories 本身即 CSF 文件中的命名导出named exports每个命名导出默认代表一个 story 对象。Storybook 为这两者分别提供了工具类型Meta对应 meta/默认导出和StoryObj对应 story 对象。本仓库各框架的Meta/StoryObj定义均基于ComponentAnnotations与StoryAnnotations这两个通用标注接口后者定义于 code/core/src/csf/story.ts再叠加各框架特有的类型变换。完整示例用 Meta 与 StoryObj 标注一个 CSF 文件下面是一个完整的、带类型标注的 CSF 文件示例这也是官方文档 typescript.mdx 中展示的核心片段 typed-csf-file.md 的内容读者可对照自己使用的框架选择对应写法。Angular 框架写法import type { Meta, StoryObj } from storybook/angular; import { Button } from ./button.component; const meta: MetaButton { component: Button, }; export default meta; type Story StoryObjButton; export const Basic: Story {}; export const Primary: Story { args: { primary: true, }, };React / Vue / 其他通用框架写法common// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import type { Meta, StoryObj } from storybook/your-framework; import { Button } from ./Button; const meta { component: Button, } satisfies Metatypeof Button; export default meta; type Story StoryObjtypeof meta; export const Basic {} satisfies Story; export const Primary { args: { primary: true, }, } satisfies Story;Web Components 框架写法import type { Meta, StoryObj } from storybook/web-components-vite; const meta: Meta { title: Button, component: demo-button, }; export default meta; type Story StoryObj; export const Basic: Story {}; export const Primary: Story { args: { primary: true, }, };三套写法体现了不同框架的差异Angular 与 Web Components 直接以组件类或自定义元素名作为泛型参数而 React/Vue 等框架则推荐配合satisfies使用typeof meta作为StoryObj的泛型参数以获得更强的类型关联详见下文。Props 类型参数泛型如何约束 argsMeta与StoryObj都是 TypeScript 中的泛型类型你可以传入一个可选的 prop 类型参数这个参数既可以是组件类型本身也可以是组件的 props 类型即Metatypeof Button中typeof Button这一部分。传入之后TypeScript 会阻止你定义非法的 arg如拼错 prop 名、给必填 prop 传错误类型让所有 decorators、play functions、loaders 的函数参数自动获得正确的类型推导。从本仓库源码可以清晰看到泛型是如何“消化”组件类型的。以 React 渲染器为例code/renderers/react/src/public-types.ts 中的定义为export type MetaTCmpOrArgs Args [TCmpOrArgs] extends [ComponentTypeany] ? ComponentAnnotationsReactRenderer, ComponentPropsTCmpOrArgs : ComponentAnnotationsReactRenderer, TCmpOrArgs;这里使用了条件类型若传入的是 React 组件类型ComponentTypeany则通过ComponentPropsTCmpOrArgs自动提取其 props 作为ComponentAnnotations的 args 类型否则直接把传入类型当作 args 类型。StoryObj的定义同文件 L47-L66则更进一步当传入的是meta对象类型时会从其component字段中infer Component提取组件类型、从其args字段中infer DefaultArgs提取默认 args从而同时约束 story 的 args 与 meta 级 args。Vue 3 的实现在 code/renderers/vue3/src/public-types.ts其ComponentPropsAndSlotsComponent还额外把组件的**插槽slots**类型并入 args 校验范围。而 Angular 的实现在 code/frameworks/angular/src/client/public-types.ts 中更复杂通过TransformComponentTypeT将 Angular 特有的InputSignal、OutputEmitterRef、ModelSignal、EventEmitter等信号/事件成员类型转换成 Storybook args 所期望的普通值类型与事件处理函数类型这也是为什么 Angular 的 stories 可以直接把 signal 输入当作普通 prop 来传值。使用 satisfies 获得更强的类型安全如果你使用 TypeScript 4.9 及以上版本可以利用新增的satisfies操作符获得更严格的类型检查不仅会检查非法invalid的 args还会对缺失的必填 args 报出类型错误。satisfies的核心价值体现在以下三点共享 play function 时的类型安全用satisfies为 story 应用类型后在多个 story 之间共享一个 play function 时不会报“play可能为 undefined”的错误——因为satisfies让 TypeScript 能够推断play函数是否已定义。连接 meta 与 story 类型satisfies允许你向StoryObj泛型传入typeof meta从而让 TypeScript 理解meta与StoryObj之间的类型关联据此从meta类型推断args类型。meta 级与 story 级 args 协同TypeScript 会理解 args 可以同时定义在 story 层与 meta 层当必填 arg 定义在 meta 层而未在 story 层重复定义时不会误报类型错误。对应到上文 common 写法的完整链路是meta用satisfies Metatypeof Button校验组件元数据Story StoryObjtypeof meta建立 story 与 meta 的连接每个 story 再用satisfies Story单独校验。注意Angular 与 Web Components 暂不支持 satisfies 增强需要特别说明的是官方文档明确标注目前还无法为 Angular 与 Web Components 提供基于satisfies操作符的额外类型安全。原因文档中的details区块有详细解释在于这两种技术都采用“类 装饰器”的方式装饰器提供的是运行时元数据而非编译期元数据因此无法在编译期判断类的某个属性是必填属性、还是带默认值的可选属性但非空或是不可空的内部状态变量。所以 Angular 与 Web Components 的 stories 仍使用上文的const meta: Meta...export const Basic: Story {}的写法而不是satisfies写法。类型化自定义 args当 args 不属于组件 props 时有时 story 需要定义组件 props 中不存在的 args例如用footerarg 来向子组件填充内容。此时可以使用 TypeScript 的交叉类型intersection type 展示了完整写法这里摘录 React 与 Angular 两种代表// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc. import type { Meta, StoryObj } from storybook/your-framework; import { Page } from ./Page; type PagePropsAndCustomArgs React.ComponentPropstypeof Page { footer?: string }; const meta { component: Page, render: ({ footer, ...args }) ( Page {...args} footer{footer}/footer /Page ), } satisfies MetaPagePropsAndCustomArgs; export default meta; type Story StoryObjtypeof meta; export const CustomFooter { args: { footer: Built with Storybook, }, } satisfies Story;import { type Meta, type StoryObj, argsToTemplate } from storybook/angular; import { Page } from ./page.component; type PagePropsAndCustomArgs Page { footer?: string }; const meta: MetaPagePropsAndCustomArgs { component: Page, render: ({ footer, ...args }) ({ props: args, template: storybook-page ${argsToTemplate(args)} ng-container footer${footer}/ng-container /storybook-page, }), }; export default meta; type Story StoryObjPagePropsAndCustomArgs; export const CustomFooter: Story { args: { footer: Built with Storybook, }, };注意这里将组合类型PagePropsAndCustomArgs直接传给Meta与StoryObj的泛型参数同时配合自定义render函数把footer渲染进子组件。args的完整使用方式可参考 args.mdx自定义渲染函数可参考 csf/index.mdx 中 “Custom render functions” 一节。框架特定提示Vue、Svelte、Angular、Web ComponentsVueVue 3 对 TypeScript 支持极佳。配合vue-tsc做类型检查、在 VSCode 中安装官方 Vue 扩展即可让*.stories.ts中导入的*.vue文件获得类型支持享受与组件代码一致的提示。另外本仓库文档还提到一个更现代的选择CSF Next目前处于 preview 阶段为 Vue 的泛型组件script langts setup genericT提供了支持——只需把类型参数直接传给组件即可import preview from ../.storybook/preview; import GenericList from ./GenericList.vue; const meta preview.meta({ // Pass the type parameter directly component: GenericList{ id: number; name: string }, }); export const Example meta.story({ args: { items: [{ id: 1, name: John Doe }], // item is correctly typed as { id: number; name: string } getLabel: (item) item.name, }, });而旧的 CSF 版本存在一个长期未解决的类型推断问题泛型T会退化为unknown。Vue 3 渲染器源码中ComponentPropsAndSlots对 props 与 slots 的合并处理见 code/renderers/vue3/src/public-types.ts。SvelteSvelte 对.svelte文件提供出色的 TypeScript 支持可用svelte-check做类型检查、配合 Svelte for VSCode 扩展获得编辑器支持同样的机制也作用于 stories 文件提供类型安全与自动补全。Svelte 的 stories 通常使用defineMeta来自storybook/addon-svelte-csf而不是默认导出参见 csf/index.mdx 中的说明。Angular 与 Web ComponentsCSF 3 对 Angular 组件只提供基础 TypeScript 支持不会推断组件类型CSF Next 可以推断 Angular 组件类型但推断出的类型是可选类型即PartialT必填 props 不会被强制。若需要强制必填 props可显式向preview.type提供组件 props 类型import preview from ../.storybook/preview; import { MyComponent } from ./my-component.component; interface MyComponentProps { requiredProp: string; optionalProp?: number; } const meta preview.type{ args: MyComponentProps }().meta({ component: MyComponent, });Web Components 同理CSF 3 不推断组件类型且Meta/StoryObj无类型泛型CSF Next 能推断但前提是自定义元素已声明在全局HTMLElementTagNameMap接口中例如使用 Lit 时并且推断类型同样是PartialT。声明方式declare global { interface HTMLElementTagNameMap { my-element: MyElement; } }强制必填 props 同样使用preview.type{ args: MyElementProps }().meta({ component: my-element })的写法。Web Components 渲染器的Meta/StoryObj目前直接透传ComponentAnnotations/StoryAnnotations且无组件泛型见 code/renderers/web-components/src/public-types.ts。关于 CSF Next 的建议官方文档在 typescript.mdx 中明确建议CSF Next目前处于 preview提供了显著改进的 TypeScript 支持——它可以自动推断组件类型大部分场景无需显式类型标注推荐所有新建的 TypeScript stories 使用 CSF Next。在 CSF Next 下你通常只需要在定义自定义 args时才需要手动补充类型例如通过preview.type{ args: ... }()。小结场景推荐写法说明Angular / Web Componentsconst meta: MetaButtontype Story StoryObjButton类 装饰器模式暂不支持satisfies增强React / Vue 等TS 4.9satisfies Metatypeof ButtonStoryObjtypeof metasatisfies Story缺失必填 args 也会报错meta 与 story 类型联动自定义 args交叉类型Props { custom?: string }传给泛型配合自定义render函数使用需要强制必填 propsCSF Next 的preview.type{ args: Props }().meta(...)适用于 Angular / Web Components 的 CSF Next 写法通过Meta与StoryObj两个工具类型及其泛型参数配合 TypeScript 4.9 的satisfies操作符你可以让 Storybook 的 stories 获得与业务代码同等级别的类型安全编辑器自动补全、非法 arg 拦截、必填 arg 缺失检测以及 decorators/play functions/loaders 的完整参数推导。这正是 typescript.mdx 所倡导的“零配置、开箱即用”的 TypeScript 故事编写体验。【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联
返回资讯列表 →