
Component Story Format 3.0
摒弃模板代码,拥抱脚本式交互!

Storybook 基于一个核心概念:**故事(story)**。每个使用 Storybook 的人都为其组件示例编写故事。Component Story Format(组件故事格式)是一种富有表现力、平台无关的格式,被从 Netflix 到 Shopify 等业界公司广泛使用。
我很高兴宣布 Component Story Format 3.0 的发布。它是 CSF 的下一个主要迭代版本,汇集了一年社区反馈,旨在大幅减少模板代码,让您可以专注于故事的本质。
- ♻️ 可扩展的故事对象,方便重用
- 🌈 默认渲染函数,提高简洁性
- 📓 自动生成标题,带来便利
- ▶️ Play 函数,实现脚本式交互
- ✅ 100% 向后兼容 CSF 2.0
CSF 3.0 今日已提供实验模式供您使用。我们期待您的反馈,以便在正式发布前将其做得更好。下面我们深入探讨细节。
CSF 到底好在哪里?
Component Story Format (CSF) 是一种简单、基于 ES6 模块的文件格式,用于组件示例。自两年前发布以来,它已大获成功。
本质上,CSF 是标准 JavaScript,与 Storybook 的 API 解耦。没有工具锁定。这意味着您可以将故事导入到您喜欢的 JS 库中。
去年,我们发布了 CSF Args(参数):动态的故事输入,开启了新的用例,例如依赖注入和自动生成的控件。
Args 已成为 Storybook 及其插件以及 Testing Library 和 Cypress 等第三方集成的强大构建模块。
更重要的是,由于 CSF 与平台无关,它现在得到了生态系统中许多优秀项目的支持,例如 RedwoodJS、React Styleguidist 和 UXPin。

CSF 3.0 新特性
Component Story Format 是一种基于 ES6 模块导出的文件格式:默认导出包含有关示例的元数据,每个命名导出都是一个示例。
在 CSF 2.0 中,命名导出始终是实例化组件的函数,并且这些函数可以附加配置选项。例如:
// CSF 2.0
export default {
title: 'components/Button',
component: Button,
};
export const Primary = (args) => <Button {...args} />;
Primary.args = { primary: true };
这声明了一个用于 Button
的 Primary
故事,它通过将 {primary: true}
展开(spread)到组件中来渲染自身。default.title
元数据说明了故事在导航层级中的位置。
以下是 CSF 3.0 的等效写法:
// CSF 3.0
export default { component: Button };
export const Primary = { args: { primary: true } };
代码少了很多!让我们逐个了解这些变化,以便理解发生了什么。
可扩展的故事对象
您可能注意到的第一件事是,在 CSF 3.0 中,命名导出是**对象**,而不是函数。这使我们能够使用 JS 展开运算符更有效地重用故事。
考虑在引言示例中添加以下内容,它创建了一个 PrimaryOnDark
故事,该故事将在深色背景下渲染:

以下是 CSF 2.0 的实现:
// CSF 2.0
export const PrimaryOnDark = Primary.bind({});
PrimaryOnDark.args = Primary.args;
PrimaryOnDark.parameters = { background: { default: 'dark' } };
Primary.bind({})
复制了故事函数,但它不复制附加到函数上的注解,因此我们必须添加 PrimaryOnDark.args = Primary.args
来继承参数(args)。
在 CSF 3.0 中,我们可以通过展开 Primary
对象来继承其所有注解:
// CSF 3.0
export const PrimaryOnDark = {
...Primary,
parameters: { background: { default: 'dark' } },
};
这看起来可能微不足道,但我们可以用它做很多巧妙的事情,正如我们将在下面看到的。
默认渲染函数
您可能注意到的下一件事是,在初始示例中,根本没有函数!这是怎么回事?
在 CSF 3.0 中,故事是对象,因此您指定故事如何渲染的方式是通过渲染函数。我们可以通过以下步骤将 CSF 2.0 示例重写为 CSF 3.0。
让我们从一个简单的 CSF 2.0 故事函数开始:
// CSF 2.0
export default {
title: 'components/Button',
component: Button,
};
export const Default = (args) => <Button {...args} />;
现在,让我们在 CSF 3.0 中将其重写为一个带有明确渲染函数的故事对象,该函数告诉故事如何渲染自身。像 CSF 2.0 一样,这使我们能够完全控制如何渲染一个组件甚至一组组件。
// CSF 3.0 - explicit render function
export const Default = {
render: (args) => <Button {...args} />
};
但在 CSF 2.0 中,许多 story
函数是相同的:它们接收默认导出中指定的组件,并将参数展开到其中。这些故事有趣之处不在于函数本身,而在于传递给函数的参数。
因此,在 CSF 3.0 中,我们为每个框架提供了默认渲染函数。如果您的操作只是将参数展开到组件中——这是最常见的情况——则根本无需指定任何渲染函数:
// CSF 3.0 - default render function
export const Default = {};
再没有比这更简单的了。告别模板代码!
自动生成标题
CSF 3.0 的另一大便利之处是自动标题生成。
// CSF 2.0
export default { title: 'components/Button', component: Button }
// CSF 3.0
export default { component: Button }
您仍然可以像在 CSF 2.0 中那样指定标题,但如果您不指定,它可以从故事文件在磁盘上的路径推断出来。
这由故事的配置方式控制。考虑旧式配置:
// .storybook/main.js
module.exports = {
stories: ['../src/**/*.stories.*']
};
现在考虑 Storybook 6.4 中可用的新式配置:
// .storybook/main.js
module.exports = {
stories: ['../src']
};
在此配置下,故事文件 ../src/components/Button.stories.tsx
将获得标题 components/Button
。
需要更多控制?以下配置将匹配自定义文件模式,并为生成的标题添加自定义前缀:
module.exports = {
stories: [
{ directory: '../src', files: '*.story.tsx', titlePrefix: 'foo' }
]
};
Play 函数
最后但同样重要的是,我们在 CSF 3.0 中引入了一个全新的功能:Play 函数。Play 函数是在故事渲染后执行的小段代码。
尽管 CSF 3.0 的其他特性优化了现有的故事结构,但 Play 函数实现了以前无法实现的情景。
考虑一个表单验证的场景:

及其 CSF 3.0 实现:
// CSF 3.0
import userEvent from '@testing-library/user-event';
export default { component: AccountForm }
export const Empty = {};
export const EmptyError = {
...Empty,
play: () => userEvent.click(screen.getByText('Submit'));
}
export const Filled = {
...Empty,
play: () => {
userEvent.type(screen.getById('user'), 'shilman@example.com');
userEvent.type(screen.getById('password'), 'blahblahblah');
}
}
export const FilledSuccess = {
...Filled,
play: () => {
Filled.play();
EmptyError.play();
}
}
Empty
故事渲染空表单。
EmptyError
通过在故事渲染后立即使用 Testing Library 模拟用户事件点击提交按钮来模拟验证错误。得益于可扩展的故事对象,这个故事继承了 Empty
故事的所有参数和配置。
Filled
故事使用 Testing Library 填写表单。
最后,FilledSuccess
故事通过首先重用 Filled
故事的 play
函数,然后点击提交按钮来展示提交状态。
我们甚至可以在 CSF 3.0 中将整个序列动画化:
这个场景也激发了 CSF 3.0 的可扩展对象语法。我们现在可以方便地复制故事的全部配置,然后只修改我们关心的部分。
征集反馈
CSF 3.0 已作为实验版本发布。我们期待您的反馈。
如果您正在使用 Storybook 6.3 或 SB 6.4 预发布版,请在您的 .storybook/main.js
配置中开启 previewCsf3
:
// .storybook/main.js
module.exports = {
features: {
previewCsfV3: true,
}
};
👉 特别注意,对于自动生成标题,您必须使用 SB 6.4,并且必须将您的 .storybook/main.js
中的 stories
配置更新为上面勾画的新格式——官方文档即将推出。
为了方便您,我们提供了一个 codemod 工具来升级您的故事:
$ npx sb@next migrate csf-2-to-3 --glob="**/*.stories.js"
对于反馈,您可以参与 GitHub 讨论 或在 Storybook Discord 的 #component-story-format
频道中提出。请查看 Github 上 标记为 csf3
的已知开放问题。
我们计划在 SB6.4 发布周期内迭代 CSF,并在准备就绪后移除功能标志。CSF 3.0 是向后兼容的,因此所有 CSF 2.0 的功能仍然可用。您可以试用而无需更改任何现有故事。
参与其中
Component Story Format 3.0 由 Michael Shilman (我!)、Tom Coleman、Gert Hengeveld 和 Pavan Sunkara 开发,并得到了整个 Storybook 社区的测试和反馈。
Storybook 是超过 1320 位社区贡献者的成果,并由顶级维护者的指导委员会组织。
如果 Storybook 让您的 UI 开发流程更轻松,请帮助 Storybook 变得更好。您可以贡献新功能、修复 bug 或改进文档。加入我们的 Discord,通过 Open Collective 支持我们,或者直接在 Github 上参与。
Component Story Format 3.0 已到来(alpha 版)
— Storybook (@storybookjs) 2021年7月7日
♻️ 可扩展的故事对象
🌈 默认渲染函数
📓 自动生成标题
▶️ Play 函数,实现脚本式交互
✅ 100% 向后兼容
所有这些都大大减少了模板代码!https://#/ky1vC6FcEL pic.twitter.com/CB7bQxNkiy