ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

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

Storybook 跨框架默认导出实战:用 Meta 与 component 字段驱动组件故事(CSF 3 与 CSF Next) Storybook 跨框架默认导出实战用 Meta 与 component 字段驱动组件故事CSF 3 与 CSF Next默认导出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 中如此归纳Thedefaultexport 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.mdx 与 docs/essentials/controls.mdx 等文档直接引用。同一概念在不同渲染器renderer下写法略有差异核心都归结为把组件放进默认导出。ReactCSF 3 推荐satisfies Meta// Button.stories.js|jsxCSF 3 import { Button } from ./Button; export default { component: Button, };TypeScript 场景下官方注释建议把框架名替换为实际使用项react-vite、nextjs、nextjs-vite 等// Button.stories.ts|tsxCSF 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 Metatypeof Button; export default meta;satisfies Metatypeof Button让对象字面量在保留其推断类型的同时接受 Meta 类型的校验component的 props 类型会被严格检查又不会被拓宽成泛化的Meta从而让 args、Controls 等功能的类型提示精确到组件 props。Angular基于类的组件声明// Button.stories.tsCSF 3 import type { Meta } from storybook/angular; import { Button } from ./button.component; const meta: MetaButton { component: Button, }; export default meta;Vue 3SFC 组件 satisfies Meta// Button.stories.jsCSF 3 import Button from ./Button.vue; export default { component: Button, };// Button.stories.tsCSF 3 import type { Meta } from storybook/vue3-vite; import Button from ./Button.vue; const meta { component: Button, } satisfies Metatypeof Button; export default meta;SvelteSvelte CSF 与标准 CSF 两种风格Svelte 渲染器同时支持社区主导的 Svelte CSF 与标准 CSF。前者用defineMeta描述组件用Story组件描述故事后者沿用本文介绍的默认导出机制。二者的目标一致声明故事属于哪个组件。!-- Button.stories.svelteSvelte CSF -- script module import { defineMeta } from storybook/addon-svelte-csf; import Button from ./Button.svelte; const { Story } defineMeta({ component: Button, }); /script// Button.stories.jsCSF 3 import Button from ./Button.svelte; export default { component: Button, };// Button.stories.tsCSF 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 Metatypeof Button; export default meta;Svelte CSF 的另一重要差异它使用原生模板语法把 children 放在Story开闭标签之间作为childrensnippet 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 Metatypeof Button; export default meta;Ember组件以字符串标识// Button.stories.js export default { component: button, };Web Components显式 title 自定义元素名Web Components 渲染器同样把组件注册为字符串自定义元素标签名代码片段中同时给出了title显式声明的形式// Button.stories.jsCSF 3 export default { component: demo-button, };// Button.stories.tsCSF 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(...)创建。其工厂链设计为definePreview→preview.meta→meta.story每一步都携带完整类型信息。下面是各框架对应的 meta 定义方式// Button.stories.tsAngular · CSF Next import preview from ../.storybook/preview; import { Button } from ./button.component; const meta preview.meta({ component: Button, });// Button.stories.tsxReact · CSF Next import preview from ../.storybook/preview; import { Button } from ./Button; const meta preview.meta({ component: Button, });// Button.stories.tsVue · CSF Next import preview from ../.storybook/preview; import Button from ./Button.vue; const meta preview.meta({ component: Button, });// Button.stories.tsWeb 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核心函数是userOrAutoTitleFromSpecifier与userOrAutoTitle。推导规则可概括如下对照 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 的标题层级决定了侧边栏的分组与顺序默认导出是这一切的元数据入口。常见问题与最佳实践结合片段与源码可提炼出以下几条可直接照做的结论默认先写export default { component }除非组件名无法自动推导出理想层级否则优先依赖自动标题保持文件精简、避免 title 手写不一致导致的拼写漂移。TypeScript 项目用satisfies Metatypeof ComponentAngular 用MetaButton泛型把 props 类型信息无缝传递给 args 与 Controls同时保留字面量类型。需要特殊分组时再显式title例如 Web Components 示例中title: Button与字符串component并存Ember、Web Components 记得用组件标识字符串而非组件类。遵守静态可分析约束不要用变量、函数返回值去拼title/component/id否则 7.0 的构建期静态分析将无法为其生成稳定 ID。Svelte 用户若使用 Svelte CSF 的defineMeta({ component })其语义与标准 CSF 默认导出等价两者可依据团队偏好选择。尝鲜 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 中的路径折叠规则你就能预测每个故事文件的最终标题写出更规范、更易维护的故事代码。创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进