返回博客

组件故事格式 3.0

告别样板,拥抱脚本化交互!

loading
Michael Shilman
@mshilman
最后更新

Storybook 基于一个核心概念:故事。所有使用 Storybook 的人都会为他们的组件示例编写故事。 组件故事格式是我们表达力强、平台无关的格式,在 Netflix 和 Shopify 等行业巨头中广泛使用。

我很高兴地宣布组件故事格式 3.0。这是 CSF 的下一个主要版本,它吸收了社区一年来的反馈,大大减少了样板代码,让您可以专注于故事的本质。

  • ♻️ 可扩展的故事对象,方便复用
  • 🌈 默认渲染函数,更简洁
  • 📓 自动生成标题,更方便
  • ▶️ Play 函数,用于脚本化交互
  • ✅ 与 CSF 2.0 100% 向后兼容

CSF 3.0 今天就可以在实验模式下使用。我们非常希望收到您的反馈,以便在正式发布前使其变得更好。下面我们来详细介绍一下。

CSF 到底有什么好处?

组件故事格式 (CSF) 是一种简单的、基于 ES6 模块的组件示例文件格式。自两年前推出以来,它一直大受欢迎。

其底层是标准的 JavaScript,与 Storybook 的 API 解耦。没有工具锁定。这意味着您可以将故事导入到您喜欢的 JS 库中。

去年我们发布了 CSF Args:动态的故事输入,开启了新的用例,例如依赖注入和自动生成的控件

Args 已成为 Storybook 及其插件的强大构建块,也成为了 Testing LibraryCypress 等第三方集成的重要组成部分。

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

CSF 3.0 新特性

组件故事格式是一种基于 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} 展开到组件中来渲染自身。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 函数是相同的:取默认导出中指定的组件,并将 args 展开到其中。这些故事有趣之处不在于函数本身,而在于传递给函数的 args。

因此,在 CSF 3.0 中,我们为每种框架提供了默认渲染函数。如果您的工作只是将 args 展开到组件中——这是最常见的情况——则无需指定任何渲染函数。

// 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 故事的所有 args 和参数。

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 的已知开放问题 here

我们计划在 SB6.4 发布周期中迭代 CSF,并在准备好后移除功能标志。CSF 3.0 向后兼容,因此所有 CSF 2.0 功能仍然可用。您可以试用而无需更改您现有的任何故事。

参与进来

组件故事格式 3.0 由 Michael Shilman(我!)、Tom ColemanGert HengeveldPavan Sunkara 开发,并得到了整个 Storybook 社区的测试和反馈。

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

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

加入 Storybook 邮件列表

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

7,468开发者及更多

我们正在招聘!

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

查看职位

热门帖子

测试复合组件

防止小改动变成大回归
loading
Varun Vachhar

如何测试组件交互

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

故事是测试

组件一切功能的单一事实来源
loading
Varun Vachhar
加入社区
7,468开发者及更多
原因为什么选择 Storybook组件驱动的 UI
文档指南教程更新日志遥测
社区插件参与进来博客
展示探索项目组件词汇表
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI