返回博客

引入 Storybook Args

下一代动态组件示例

loading
Michael Shilman
@mshilman
最后更新于

Storybook 是世界上最流行的 UI 组件工作台。Airbnb、Slack、Lyft、IBM、Shopify 以及业内数千个顶尖团队都在使用它。

Storybook 的核心是 stories 的目录:捕获独立 UI 组件状态的微小 Javascript 函数,这使得它们对于交互式开发、测试和文档非常有用。

现在在 Storybook 6.0 中,我们很高兴地推出 Storybook Args,这是故事编写的一个基础性改进。Args 允许故事接收动态数据作为输入参数,解锁了故事的新的水平的人体工程学、可移植性重用性

Args 与您现有的故事兼容,但解锁了新功能

  • 🗜 减少故事的大小和复杂性
  • 🚚 重用故事中的固定数据
  • ♻️ 更广泛的工具中复用故事

它们还为强大的下一代 Storybook 插件打开了大门,我们将在后续一系列博文中介绍它们。


💡 为什么需要动态数据?

Storybook 的组件故事格式(CSF)是组件示例的新兴标准,在 Storybook 5.2 中引入。CSF 文件是 ES6 模块,不包含任何 Storybook 特有的扩展。因此,它们简单、可移植且面向未来。

export const Basic = () => (
  <Button label='hello' />
);

编写一个 CSF 故事很简单。您只需实例化您想展示状态的组件,将其包装在一个函数中,并命名它。

但它可以改进。Tom Coleman 和我在研究 CSF 在 45,000 多个 Github 仓库中的使用情况后,有了一个顿悟:如果故事接受动态数据作为输入,它将解锁无数新的用例。

考虑我们之前示例的以下迭代

export const Basic = (args) => <Button {...args} />;
Basic.args = { label: 'hello' };

这个新版本从“环境中”接收一个 Args 对象,并在该对象中声明它希望接收的初始数据。

这种间接的技巧看起来微不足道,但它是一个强大的抽象,支撑着下一代 Storybook 功能。在这篇博文中,我们将重点介绍 Args 如何通过不同方式使故事编写更有效率。

动态数据使您能够实时编辑组件

🗜 减少故事样板代码

Args 通过更清晰地分离故事的数据展示逻辑,减少了用户需要为每个故事编写和维护的代码。

考虑以下 CSF v1 故事

// Button.stories.js

export const Text = () => (
  <Button label="hello" background="#ff0" />
);

export const Emoji = () => (
  <Button label="😀 😎 👍 💯" background="#ff0" />
);

现在考虑使用 Args 的相同故事

// Button.stories.js

const Template = (args) => <Button {...args} />;

export const Text = Template.bind({});
Text.args = { label: 'hello', background: '#ff0' };

export const Emoji = Template.bind({});
Emoji.args = { ...Text.args, label: '😀 😎 👍 💯' };

故事函数的样板代码在故事之间重用:Template.bind({}) 复制了该函数,减少了代码重复

同样,...Text.args 复制了数据,减少了数据重复

在为其他框架编写故事时,好处变得更加明显。考虑 Vue 的等效示例

// Button.stories.js

const Template = (args) => ({
  props: Object.keys(args),
  components: { MyButton },
  template: `
    <my-button :background="background">
     {{label}}
    </my-button>
  `
});

export const Text = Template.bind({});
Text.args = { label: 'hello', background: '#ff0' };

export const Emoji = Template.bind({});
Emoji.args = { ...Text.args, label: '😀 😎 👍 💯' };

这种用于 Args 故事的模式与早期的 CSF 版本有所不同,但我们认为您会喜欢它。如果您更喜欢“旧”的编写方式,或者选择增量式采用,Storybook 完全向后兼容。

🚚 在故事中重用固定数据

Args 的第二个主要用户好处是,它提供了一种规范的方式来组织和共享组件的固定数据。

您可能熟悉编程中的 DRY 原则:不要重复自己。但在实践中,我们在故事中看到了大量的重复。

考虑一个假设任务列表的复合组件

// TaskList.stories.js

const owner = { login: 'shilman', avatar: ... };
export const Basic = () => (
  <TaskList items={[
    { title: 'checked item', checked: true, owner },
    { title: 'checked item', checked: true, owner },
    { title: 'unchecked item', checked: false, owner },
    { title: 'unassigned item', checked: false,  },
  ]}/>
);

现在考虑 Args 的等效写法

// TaskList.stories.js

import { Checked, Unchecked, Unassigned } from '../Task.stories';

const Template = (args) => <TaskList {...arg} />;

export const Basic = Template.bind({});
Basic.args = { 
  items: [Checked.args, Checked.args, Unchecked.args, Unassigned.args]
};

在 Args 示例中,当 Task 组件的签名发生变化时,您只需要更改 Tasks 故事以反映新的模式。由于您的 Args 故事重用了固定数据,模式更改将沿着故事层级向上传播,从而显著降低维护成本。

这种数据重用在为大型应用程序构建 Storybook 时特别有用,其中组件按层级组织。

Args 的大部分设计灵感来自 Chromatic 的 storybook,其中包含数千个故事,范围从原子组件一直到完整的应用程序页面。

Args 将这些经验教训编码到故事语言本身中,融入了作为顶尖实践者多年开发和使用 Storybook 积累的来之不易的最佳实践。

♻️ 在其他工具中复用故事

最后一个好处是,Args 故事比以前更具可移植性。

组件故事格式的一个设计目标是创建高度可移植的组件示例。在我们的公告文章中,我们展示了如何在 Jest 单元测试中复用 CSF 故事,并描绘了前端工具领域更广阔的互操作性前景。

随着 CSF 在整个生态系统中得到采用,这张蓝图正在成为现实。它被内置于热门的新 React 框架 RedwoodJS 中。它为强大的在线 IDE webcomponents.dev 提供支持。截至撰写本文时,它也正被整合到至少一个流行的设计工具中。

Args 通过从您的故事中移除插件依赖性,将互操作性向前推进了一步。考虑以下使用 actionsknobs 的 CSF v1 故事,这是 Storybook 最受欢迎的两个插件

import { action } from '@storybook/addon-actions';
import { text } from '@storybook/addon-knobs';

export default { title: 'Button' };

export const Text = () => (
  <Button
    onClick={action('onClick')}
    label={text('label', 'Hello')}
  />
);

这个 ES6 模块是可移植的,但它依赖于 @storybook/addon-actions@storybook/addon-knobs,这两个插件都严重依赖 Storybook 的运行时环境。

现在考虑 Args 的等效写法

const Template = (args) => <Button {...args} />;

export const Text = Template.bind({});
Text.args = { label: 'Hello' }

注意,这个故事不再依赖于 @storybook/* 包。Args 由“环境”提供,因此即使没有直接的故事依赖,我们也可以保留 actions 和 knobs 式的功能!在我们后续的博文中介绍自动生成的控件动作时,我们将详细解释这是如何工作的。

移除这些 Storybook 依赖性提高了可移植性,使得故事更容易在外部设计、原型制作、开发和测试工具中使用。

考虑上面那个 Args 故事如何在 Jest 中复用

import { render, fireEvent } from '@testing-library/react';
import { Text } from './Button.stories';

it('should respond to click events', () => {
  const handleClick = jest.fn();
  
  const instance = render(
    <Text {...Text.args} onClick={handleClick} />
  );
  
  fireEvent.click(instance.container.firstChild);
  expect(handleClick).toHaveBeenCalled();
});

为使用 knobs 和 actions 的原始 CSF 故事编写相同的测试将需要复杂的模拟。现在不再需要了!我们很高兴将基于 Args 的组件示例推广到整个前端生态系统。

⚡ 1 分钟安装

在过去几个月使用 Args 后,我们认为所有用户都应该立即升级。立即在 Storybook 6.0 中试用吧!

升级现有的 Storybook 项目

npx sb upgrade

或将 Storybook 引入现有应用

npx sb init

要创建您的第一个 Args 故事,请创建一个将 Args 对象作为第一个输入的函数,然后用您希望接收的数据注释该函数

import { Button } from '@storybook/react/demo';

export default { title: 'Button/Args', component: Button };

export const Basic = (args) => <Button {...args} />;
Basic.args = { children: 'hello' };

Voilà!您已经编写了您的第一个 Args 故事。但这只是冰山一角。Args 还极大地增强了插件功能,并为 Storybook 带来了许多新特性,我们将在未来几周内详细介绍。

在下方订阅以了解 Args 如何实现


参与其中

Args 由 Michael Shilman(我!)和 Tom Coleman 开发,灵感来自 addon-docs/knobs/context 的贡献者,包括 Norbert de LangenFilipp RiabchunAtanas StoyanovLeo Y. Li

Storybook 由 1000 多名开源贡献者维护,并由顶尖维护者组成的指导委员会指导。如果您有兴趣贡献,请在 GitHub 上查看 Storybook,创建议题,或提交拉取请求。在 Open Collective 上捐赠。在 Discord 中与我们聊天 — 通常有维护者在线。

加入 Storybook 邮件列表

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

7,180开发者及仍在增长

我们正在招聘!

加入 Storybook 和 Chromatic 背后的团队。构建被数十万开发者用于生产环境的工具。远程优先。

查看职位

热门文章

Storybook Controls

无需代码实时编辑 UI 组件
loading
Michael Shilman

Zero-config Storybook

简单设置,即时提升效率
loading
Michael Shilman

Storybook 5.3

更快构建生产级设计系统
loading
Michael Shilman
加入社区
7,180开发者及仍在增长
为什么为什么选择 Storybook组件驱动的 UI
文档指南教程更新日志遥测
社区插件参与其中博客
案例展示探索项目组件术语表
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI