Storybook 组件故事元数据(meta)默认导出全指南:Component Story Format 中的 default export 与 title/component 用法
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
导读
在 Storybook 的 Component Story Format(CSF)中,故事文件通过默认导出(default export)的meta对象来描述组件级元数据——它决定了组件如何出现在侧边栏、自动文档如何生成、addon 如何识别组件与参数。本文以仓库中的官方代码片段 button-story-default-export.md 为主体,系统讲解meta中title与component两个核心字段的语义、自动标题的生成原理,并给出 Angular、Svelte、Web Components、React、Vue、HTML 等全部渲染器的可运行写法。读完后你将能针对任意受支持的框架,正确书写标准 CSF 3 与实验性 CSF Next 语法的默认导出。
一、为什么需要默认导出:meta 是故事的"组件级配置"
一个 CSF 故事文件通常包含两类导出(见 docs/writing-stories/index.mdx):
- 默认导出(default export,即
meta):描述"这一个故事文件服务于哪个组件",包括组件名称、所在层级,以及会被 addon 使用的公共信息; - 命名导出(named exports):描述组件的每一个具体状态,也就是一个个 story。
Storybook 依靠这份默认导出元数据来决定"如何把你的故事列进侧边栏",以及"addon 需要哪些组件信息"。例如下面这种最常见、最通用的形式(适用于任意框架,文件可为.js或.jsx):
import { Button } from './Button'; export default { /* 👇 title 属性是可选的。 * 省略后 Storybook 会依据文件路径自动生成标题(auto-title)。 */ title: 'Button', component: Button, };对应的 TypeScript 写法则会引入渲染器专属的Meta类型,并用satisfies做类型收窄(将鼠标悬停在meta上即可获得组件 props 级别的类型提示):
// 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 = { /* 👇 title 属性是可选的。 */ title: 'Button', component: Button, } satisfies Meta<typeof Button>; export default meta;二、核心字段剖析:title 与 component
2.1title:控制侧边栏层级(可选)
title决定该组件在侧边栏中的展示位置,它是可选的。代码片段中每一份示例都保留了相同注释:
👇 The title prop is optional.
当省略title时,Storybook 从故事文件的物理位置推导标题,即隐式(implicit)组织方式;而当显式提供title时,组件被固定放置在指定的层级位置(explicit 方式)。更完整的讨论见 naming-components-and-hierarchy.mdx,其中页面正是直接引用了本文所依据的 button-story-default-export.md 作为"命名故事"的示范代码。
另外值得注意:自 Storybook 7.0 起,故事标题是在构建期被静态分析的。默认导出必须包含一个可被静态读取的title属性,或一个能据此计算出自动标题的component属性;若要用id自定义故事 URL,该id也必须静态可读(依据 docs/writing-stories/index.mdx)。
2.2component:把故事"绑定"到真实组件(省略 title 时的自动标题来源)
component将故事文件与实际的组件类/函数关联起来,是自动标题(auto-title)计算的数据来源。它在仓库侧边栏生成侧栏结构,同时支撑 autodocs、Args 表、controls 等 addon 对组件 props 的解析。
从实现上可以看得更清楚。autoTitle.ts 中userOrAutoTitleFromSpecifier的核心逻辑是:当文件命中 stories glob 时,如果存在用户提供的userTitle就优先使用它;否则从文件路径中切出后缀,连同titlePrefix一起清洗后拼成标题:
if (!userTitle) { const suffix = normalizedFileName.replace(directory, ''); let parts = pathJoin([titlePrefix, suffix]).split('/'); parts = sanitize(parts); return parts.join('/'); } if (!titlePrefix) { return userTitle; } return pathJoin([titlePrefix, userTitle]);其中sanitize负责去除.stories.js/.stories.ts之类的文件后缀,并折叠atoms/button/button.stories.js中重复的目录段(例如src/button/button.stories.js→button),保证Button/Button.stories.js不会被渲染成Button/Button。这也解释了代码注释中"省略 title 即自动生成标题"的底层机制。
三、type 层面的保证:框架专属Meta类型
在每个渲染器的public-types.ts中,Meta都被定义成该渲染器 + 组件参数的组合类型,例如 React:
- code/renderers/react/src/public-types.ts 中定义:
export type Meta<TCmpOrArgs = Args> = [TCmpOrArgs] extends [ComponentType<any>] ? ComponentAnnotations<ReactRenderer, ComponentProps<TCmpOrArgs>> : ComponentAnnotations<ReactRenderer, TCmpOrArgs>;也就是说Meta<Button>会把组件 props 自动注入类型系统,让你在写args、play时获得编译期校验。类似的类型定义同样存在于 Vue3(code/renderers/vue3/src/public-types.ts)、Web Components(code/renderers/web-components/src/public-types.ts)、HTML(code/renderers/html/src/public-types.ts)、Angular(code/frameworks/angular/src/client/public-types.ts)等各渲染器实现中。
在浏览器构建侧,get-story-id.ts 会读取 story 文件路径,优先取用户title,否则调用上述自动标题逻辑得到autoTitle,再由toId(autoTitle, storyName)生成稳定的 story id——title一旦变化,故事的 URL id 也会随之变化。
四、按框架逐一详解默认导出写法
下面按渲染器组织,完整覆盖官方片段给出的全部变体。同一渲染器内可能存在两种语法世代:CSF 3(稳定)与标有 🧪 的CSF Next(实验性)。
4.1 Angular
Angular 的标准 CSF 3 使用@storybook/angular提供的Meta类型,并把组件类作为component传入:
import type { Meta } from '@storybook/angular'; import { Button } from './button.component'; const meta: Meta<Button> = { /* 👇 title 属性是可选的。省略后可从文件路径自动生成标题 */ title: 'Button', component: Button, }; export default meta;实验性的 CSF Next 语法则改为从.storybook/preview导入preview,再调用preview.meta({ ... })构造同样的元数据对象(该工厂方法会被后续语法继续统一使用,见下方 React / Vue / Web Components 各小节):
import preview from '../.storybook/preview'; import { Button } from './button.component'; const meta = preview.meta({ /* 👇 title 属性是可选的。 */ title: 'Button', component: Button, });4.2 Svelte
Svelte 用户有两种语法可写:专用的Svelte CSF(@storybook/addon-svelte-csf的defineMeta),以及同样适用于 Svelte 的标准CSF 3。
Svelte CSF(.svelte故事文件,写在<script module>中),JavaScript 与 TypeScript 写法一致:
<script module> import { defineMeta } from '@storybook/addon-svelte-csf'; import Button from './Button.svelte'; const { Story } = defineMeta({ /* 👇 title 属性是可选的。 */ title: 'Button', component: Button, }); </script><script module> import { defineMeta } from '@storybook/addon-svelte-csf'; import Button from './Button.svelte'; const { Story } = defineMeta({ /* 👇 title 属性是可选的。 */ title: 'Button', component: Button, }); </script>Svelte 的标准 CSF 3,JavaScript 使用对象字面量直接导出:
import Button from './Button.svelte'; export default { /* 👇 title 属性是可选的。 */ title: 'Button', component: Button, };TypeScript 版本则像 React 一样引入对应渲染器(svelte-vite或sveltekit)的Meta类型并配合satisfies:
// Replace your-framework with svelte-vite or sveltekit import type { Meta } from '@storybook/your-framework'; import Button from './Button.svelte'; const meta = { /* 👇 title 属性是可选的。 */ title: 'Button', component: Button, } satisfies Meta<typeof Button>; export default meta;4.3 HTML
HTML 渲染器没有"组件类",而是传入组件工厂函数createButton及其参数类型:
import { createButton } from './Button'; export default { /* 👇 title 属性是可选的。 */ title: 'Button', };注意:HTML 的 JS 示例允许只写title而不写component——因为无框架环境下组件实例由渲染函数(story 的render)产生。TypeScript 版本把ButtonArgs作为Meta的类型参数传入:
import type { Meta } from '@storybook/html'; import { createButton, ButtonArgs } from './Button'; const meta: Meta<ButtonArgs> = { /* 👇 title 属性是可选的。 */ title: 'Button', }; export default meta;4.4 Web Components
Web Components 渲染器的特别之处在于component字段传入的是自定义元素标签名字符串(如'demo-button'),而不是类引用:
CSF 3,JavaScript:
export default { title: 'Button', component: 'demo-button', };CSF 3,TypeScript(Meta不携带类型参数):
import type { Meta } from '@storybook/web-components-vite'; const meta: Meta = { title: 'Button', component: 'demo-button', }; export default meta;CSF Next,JavaScript 与 TypeScript 均通过preview.meta构造:
import preview from '../.storybook/preview'; const meta = preview.meta({ /* 👇 title 属性是可选的。 */ title: 'Button', component: 'demo-button', });import preview from '../.storybook/preview'; const meta = preview.meta({ /* 👇 title 属性是可选的。 */ title: 'Button', component: 'demo-button', });4.5 React
React 的稳定 CSF 3 写法即第二节中的通用形式(satisfies Meta<typeof Button>)。官方片段同时补充了 CSF Next 语法——从项目.storybook/preview中导入preview并调用preview.meta,TypeScript 与 JavaScript 均如此:
import preview from '../.storybook/preview'; import { Button } from './Button'; const meta = preview.meta({ /* 👇 title 属性是可选的。 */ title: 'Button', component: Button, });import preview from '../.storybook/preview'; import { Button } from './Button'; const meta = preview.meta({ /* 👇 title 属性是可选的。 */ title: 'Button', component: Button, });4.6 Vue 3
Vue 3 故事文件默认导入.vue单文件组件。CSF Next 语法同样基于preview.meta:
import preview from '../.storybook/preview'; import Button from './Button.vue'; const meta = preview.meta({ /* 👇 title 属性是可选的。 */ title: 'Button', component: Button, });import preview from '../.storybook/preview'; import Button from './Button.vue'; const meta = preview.meta({ /* 👇 title 属性是可选的。 */ title: 'Button', component: Button, });五、CSF Next 🧪 语法速览:preview.meta从哪来
从上文可以看到,凡是标注 CSF Next 的示例都把import preview from '../.storybook/preview'作为第一步,再以preview.meta({ ... })替代const meta = { ... }。这一模式把"全局 project annotations(装饰器、参数)"与"组件 meta"通过preview实例统一起来,是仓库中正在演进的下一代 CSF 编写入口;关于其设计动机与完整说明可继续阅读 csf-next.mdx。由于它仍处于实验阶段,跨大版本升级场景下应留意该语法与稳定版 CSF 3 的迁移关系。
六、什么时候该省略 title:自动标题与侧边栏组织建议
官方片段中反复强调"title是可选的",因此实际工程中可按以下原则取舍:
- 组件文件与目录同名或按功能目录组织(如
src/atoms/Button/Button.stories.ts):推荐省略title,让侧边栏自动镜像目录结构。实现上,autoTitle.ts 的sanitize会智能地去重与去后缀(Button.stories→Button),这正是官方注释指向的"自动生成标题"能力。 - 需要固定层级/分组、或与目录结构解耦:显式写
title,例如'DesignSystem/Button'(用/表示层级,可直接用于侧边栏分组与 button-story-grouped.md 所展示的目录式组织)。 - 无论哪种方式,
component都建议保留:它是 addon(如 Controls、Docs、a11y)识别 props、生成自动文档的锚点。
若你正在理解标题在索引与 URL 生成中的角色,可顺着调用链阅读 get-story-id.ts:它把解析得到的标题交给sanitize与toId,最终拼出故事 id 与 kind,即"标题即结构"这一设计在实现层的落点。
七、结语
一份正确的默认导出只需要记住两条黄金规则:需要明确位置就给title,不给就让 Storybook 依据文件路径自动推导;始终把真实的component关联上,为类型安全与 addon 能力提供基础。无论是 Angular、Svelte、Vue、Web Components、HTML 还是 React,本文给出的各渲染器变体都可直接复制进你的Button.stories.*文件运行。原始多语言对照版本保存在 docs/_snippets/button-story-default-export.md,其中每种渲染器的差异点也正是本节对照表所梳理的内容,可作为团队规范落地时的唯一参考源。
【免费下载链接】storybookStorybook is the industry standard workshop for building, documenting, and testing UI components in isolation项目地址: https://gitcode.com/GitHub_Trending/st/storybook
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考