首页
/ Storybook 跨框架默认导出实战:用 Meta 与 component 字段驱动组件故事(CSF 3 与 CSF Next)

Storybook 跨框架默认导出实战:用 Meta 与 component 字段驱动组件故事(CSF 3 与 CSF Next)

2026-09-07 17:19:33作者:郦嵘贵Just

默认导出(default export)是 Storybook 组件故事格式(Component Story Format,即 CSF)的心脏:它声明"这份故事文件为哪个组件编写",从而驱动故事列表、自动标题、Docs 与各类 addon 的元数据。这篇指南以官方代码片段为骨架,逐一给出 Angular、React、Vue、Svelte、Solid、Preact、Ember、Web Components 各框架下带 component 字段的默认导出写法,并深入源码解释自动标题的推导规则,帮助你写出可被 Storybook 正确索引、结构清晰且类型安全的故事文件。读完你将掌握如何仅凭"组件即元数据"的最佳实践,就能让故事自动获得稳定标题与完整功能支持。

默认导出在 CSF 中的定位

CSF 是一种基于 ES6 模块的标准,易于编写且可在工具间移植。其关键组成有两个:

  • meta(默认导出):描述组件及整个故事文件的公共元信息;
  • 具名导出:描述一个个具体的故事(story)。

默认导出中的元数据控制 Storybook 如何在侧边栏中列出你的故事,并为各类 addon 提供必要信息。官方文档在 docs/writing-stories/index.mdx 中如此归纳:"The default export metadata controls how Storybook lists your stories and provides information used by addons."

// Button.stories.js|ts —— 概念示意
export default {
  component: Button,
};

其中 component 字段是默认导出里最核心的配置之一:它指向故事要渲染的组件,也是本篇文章要展开的所有代码示例的公共主题。

只写 component:自动标题让 meta 保持极简

在绝大多数框架与写法中,默认导出只需要一个 component 字段即可。Storybook 会从文件路径与组件信息静态推导故事的标题(title),因此无需手工编写 title

这一点在文档中有明确约束,docs/writing-stories/index.mdx 的提示框指出:

从 Storybook 7.0 开始,故事标题作为构建过程的一部分被静态分析。默认导出必须包含一个可以被静态读取的 title 属性,或者包含一个可由其推导出自动标题的 component 属性。使用 id 属性自定义故事 URL 也必须可静态读取。

也就是说,component 是"静态可分析性"要求下的合规写法:你给出组件引用,Storybook 根据文件在项目中的位置自动计算层级化标题,例如位于 src/components/Button/Button.stories.js 的文件会得到标题 components/Button

各框架默认导出写法速查

下面的代码片段摘自仓库中的 button-story-default-export-with-component.md,该片段被 docs/writing-stories/index.mdxdocs/essentials/controls.mdx 等文档直接引用。同一概念在不同渲染器(renderer)下,写法略有差异,核心都归结为"把组件放进默认导出"。

React:CSF 3 推荐 satisfies Meta

// Button.stories.js|jsx(CSF 3)
import { Button } from './Button';

export default {
  component: Button,
};

TypeScript 场景下,官方注释建议把框架名替换为实际使用项(react-vite、nextjs、nextjs-vite 等):

// Button.stories.ts|tsx(CSF 3)
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta } from '@storybook/your-framework';

import { Button } from './Button';

const meta = {
  component: Button,
} satisfies Meta<typeof Button>;

export default meta;

satisfies Meta<typeof Button> 让对象字面量在保留其推断类型的同时接受 Meta 类型的校验:component 的 props 类型会被严格检查,又不会被拓宽成泛化的 Meta,从而让 args、Controls 等功能的类型提示精确到组件 props。

Angular:基于类的组件声明

// Button.stories.ts(CSF 3)
import type { Meta } from '@storybook/angular';

import { Button } from './button.component';

const meta: Meta<Button> = {
  component: Button,
};

export default meta;

Vue 3:SFC 组件 + satisfies Meta

// Button.stories.js(CSF 3)
import Button from './Button.vue';

export default {
  component: Button,
};
// Button.stories.ts(CSF 3)
import type { Meta } from '@storybook/vue3-vite';

import Button from './Button.vue';

const meta = {
  component: Button,
} satisfies Meta<typeof Button>;

export default meta;

Svelte:Svelte CSF 与标准 CSF 两种风格

Svelte 渲染器同时支持社区主导的 Svelte CSF 与标准 CSF。前者用 defineMeta 描述组件,用 Story 组件描述故事;后者沿用本文介绍的默认导出机制。二者的目标一致:声明"故事属于哪个组件"。

<!-- Button.stories.svelte(Svelte CSF) -->
<script module>
  import { defineMeta } from '@storybook/addon-svelte-csf';

  import Button from './Button.svelte';

  const { Story } = defineMeta({
    component: Button,
  });
</script>
// Button.stories.js(CSF 3)
import Button from './Button.svelte';

export default {
  component: Button,
};
// Button.stories.ts(CSF 3)
// Replace your-framework with svelte-vite or sveltekit
import type { Meta } from '@storybook/your-framework';

import Button from './Button.svelte';

const meta = {
  component: Button,
} satisfies Meta<typeof Button>;

export default meta;

Svelte CSF 的另一重要差异:它使用原生模板语法把 children 放在 <Story> 开闭标签之间(作为 children snippet prop 传给组件),而非通过 args,详见 docs/writing-stories/index.mdx

Preact

// Button.stories.js|jsx
import { Button } from './Button';

export default {
  component: Button,
};

Solid

// Button.stories.js|jsx
import { Button } from './Button';

export default {
  component: Button,
};
// Button.stories.ts|tsx
import type { Meta } from 'storybook-solidjs-vite';

import { Button } from './Button';

const meta = {
  component: Button,
} satisfies Meta<typeof Button>;

export default meta;

Ember:组件以字符串标识

// Button.stories.js
export default {
  component: 'button',
};

Web Components:显式 title + 自定义元素名

Web Components 渲染器同样把组件注册为字符串(自定义元素标签名),代码片段中同时给出了 title 显式声明的形式:

// Button.stories.js(CSF 3)
export default {
  component: 'demo-button',
};
// Button.stories.ts(CSF 3)
import type { Meta } from '@storybook/web-components-vite';

const meta: Meta = {
  title: 'Button',
  component: 'demo-button',
};

export default meta;

注意区别:Angular、React、Vue、Svelte、Solid、Preact 等框架把组件构造函数/类/对象作为 component,而 Ember 与 Web Components 由于组件模型不同,component 字段填写的是组件标识字符串(如 'button''demo-button')。

CSF Next(实验性):用 preview.meta 取代默认导出

仓库文档还收录了一种实验性的下一代写法 CSF Next(文档中以 🧪 标记,源自 RFC 讨论)。它与 CSF 3 最大的差别在于:meta 对象不再作为 default export 导出,而是通过由 docs/api/csf/csf-next.mdx 中的 definePreview 工厂返回的 preview 调用 preview.meta(...) 创建。

其工厂链设计为 definePreviewpreview.metameta.story,每一步都携带完整类型信息。下面是各框架对应的 meta 定义方式:

// Button.stories.ts(Angular · CSF Next)
import preview from '../.storybook/preview';

import { Button } from './button.component';

const meta = preview.meta({
  component: Button,
});
// Button.stories.tsx(React · CSF Next)
import preview from '../.storybook/preview';

import { Button } from './Button';

const meta = preview.meta({
  component: Button,
});
// Button.stories.ts(Vue · CSF Next)
import preview from '../.storybook/preview';

import Button from './Button.vue';

const meta = preview.meta({
  component: Button,
});
// Button.stories.ts(Web Components · CSF Next)
import preview from '../.storybook/preview';

const meta = preview.meta({
  title: 'Button',
  component: 'demo-button',
});

需要注意,CSF Next 目前属于实验性 API,使用时需关注对应 feature flag 与发布说明;标准 CSF 3 仍是仓库中所有官方文档示例(包括 Controls、args 等章节)的主力写法,因此 export default meta 依旧是最稳妥的默认选择。

源码级解析:component 是如何"变成"标题的

只写一个 component 字段就能工作,背后是 Storybook 核心的**自动标题(auto-title)**机制。其实现位于 code/core/src/shared/story-index/autoTitle.ts,核心函数是 userOrAutoTitleFromSpecifieruserOrAutoTitle

推导规则可概括如下(对照 autoTitle.test.ts 中的用例):

  • 故事文件必须命中 stories 匹配器(如 ./src 目录下的 **/*.stories.*),否则返回空;
  • 若用户未提供显式 title,则去掉 directory 前缀后,把剩余路径按 / 拆分作为标题层级;
  • sanitize 处理冗余:移除尾部的 .stories.ts 之类的扩展;如果末段与倒数第二段同名(如 button/button.stories.js),会折叠重复段,得到 to/buttonindex.stories.js 也会被折叠为上一级目录名;
  • 支持 titlePrefix(在 directory 之外统一加前缀,例如 atoms/ 前缀会拼成 atoms/to/file);
  • 路径统一用 slash 转换为 Unix 风格,兼容 Windows 反斜杠。

例如测试用例 './path/to/button/button.stories.js' 得到标题 to/button,而 './path/to-my/file.stories.js' 得到 to-my/file。此外,从 Storybook 7.0 起该逻辑作为构建期静态分析的一部分运行,所以 component/title/id 都必须是字面量可静态读取的,不能是运行时动态拼接的值。

这个由自动标题生成的故事 ID 最终在 code/core/src/common/utils/get-story-id.ts 中结合 toId 转成稳定的 storyId,并用于预览端的索引;在预览运行时,Storybook 通过 code/core/src/preview-api/modules/store/StoryStore.ts 接收这些可能在服务端(core-server)由自动标题生成的标题。从源码结构可以推断:文件路径 → 自动标题 → storyId 是一条端到端的数据链,而 component 字段正是这条链上"由组件推导标题"的锚点。

component 字段的其他用途:Docs、Controls 与 addon 元数据

component 不止用于生成标题,它还承担以下职责:

  • Docs / Autodocs:文档块(<Meta of={...}> 等)需要把组件与 CSF 文件关联起来。在 code/core/src/preview-api/modules/preview-web/docs-context/DocsContext.ts 中可以看到,csfFile.meta.component 被用于在多个 CSF 文件间建立"组件即来源"的关联判断,从而在文档页定位该组件的 stories 与 meta。
  • Controls 等交互式 addondocs/essentials/controls.mdx 多次复用的正是本文的默认导出片段。只有 meta 正确指向组件,Storybook 才能读取组件 props/入参类型,为 args 自动生成对应的控制面板(输入框、下拉、开关等)。
  • Sidebar 层级组织:meta 的标题层级决定了侧边栏的分组与顺序,默认导出是这一切的元数据入口。

常见问题与最佳实践

结合片段与源码,可提炼出以下几条可直接照做的结论:

  1. 默认先写 export default { component }:除非组件名无法自动推导出理想层级,否则优先依赖自动标题,保持文件精简、避免 title 手写不一致导致的拼写漂移。
  2. TypeScript 项目用 satisfies Meta<typeof Component>(Angular 用 Meta<Button> 泛型),把 props 类型信息无缝传递给 args 与 Controls,同时保留字面量类型。
  3. 需要特殊分组时再显式 title:例如 Web Components 示例中 title: 'Button' 与字符串 component 并存;Ember、Web Components 记得用组件标识字符串而非组件类。
  4. 遵守静态可分析约束:不要用变量、函数返回值去拼 title/component/id,否则 7.0+ 的构建期静态分析将无法为其生成稳定 ID。
  5. Svelte 用户:若使用 Svelte CSF 的 defineMeta({ component }),其语义与标准 CSF 默认导出等价;两者可依据团队偏好选择。
  6. 尝鲜 CSF Next:确认你的版本启用实验语法后再使用 preview.meta,正式项目仍以 CSF 3 的 default export 为主。

小结

默认导出(meta)是 CSF 文件的结构骨架,而 component 字段则是把故事绑定到真实组件的最小必要信息。无论是 Angular/React/Vue/Solid 的组件引用式写法,还是 Ember/Web Components 的字符串标识式写法,亦或 Svelte CSF 的 defineMeta 与实验性 CSF Next 的 preview.meta,其本质一致:用"组件即元数据"换取自动标题、自动文档与 addon 全链路支持。理解 auto-title 在 code/core/src/shared/story-index/autoTitle.ts 中的路径折叠规则,你就能预测每个故事文件的最终标题,写出更规范、更易维护的故事代码。

登录后查看全文
热门项目推荐
相关项目推荐