返回博客

Component Story Format 3.0

摒弃模板代码,拥抱脚本式交互!

loading
Michael Shilman
@mshilman
最后更新

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 LibraryCypress 等第三方集成的强大构建模块。

更重要的是,由于 CSF 与平台无关,它现在得到了生态系统中许多优秀项目的支持,例如 RedwoodJSReact StyleguidistUXPin

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 };

这声明了一个用于 ButtonPrimary 故事,它通过将 {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.3SB 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 ColemanGert HengeveldPavan Sunkara 开发,并得到了整个 Storybook 社区的测试和反馈。

Storybook 是超过 1320 位社区贡献者的成果,并由顶级维护者的指导委员会组织。

如果 Storybook 让您的 UI 开发流程更轻松,请帮助 Storybook 变得更好。您可以贡献新功能、修复 bug 或改进文档。加入我们的 Discord,通过 Open Collective 支持我们,或者直接在 Github 上参与。

加入 Storybook 邮件列表

获取最新新闻、更新和发布信息

7,180开发者及仍在增长

我们正在招聘!

加入 Storybook 和 Chromatic 的团队。构建数百万开发者在生产中使用的工具。远程优先。

查看职位

热门文章

测试复合组件

防止微小改动导致重大回退
loading
Varun Vachhar

如何测试组件交互

学习如何模拟用户行为并运行功能检查
loading
Varun Vachhar

故事就是测试

组件所有行为的单一事实来源
loading
Varun Vachhar
加入社区
7,180开发者及仍在增长
原因Storybook 的优势组件驱动UI
文档指南教程更新日志遥测
社区插件参与其中博客
案例展示探索项目组件词汇表
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI