返回博客

介绍 Storybook Args

下一代动态组件示例

loading
Michael Shilman
@mshilman
最后更新

Storybook 是世界上最受欢迎的 UI 组件工作室。它被 Airbnb、Slack、Lyft、IBM、Shopify 以及行业内成千上万的顶级团队使用。

Storybook 的核心是组件的目录:小的 Javascript 函数,捕获隔离的 UI 组件状态,使其在交互式开发、测试和文档方面极其有用。

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

Args 与您现有的组件兼容,但解锁了新的功能。

  • 🗜 减少组件的大小和复杂性
  • 🚚 在不同组件中复用固定数据
  • ♻️ 在更广泛的工具中回收组件

它们也为强大的下一代 Storybook 插件打开了大门,我们将在后续的帖子中介绍这些插件。


💡 为什么需要动态数据?

Storybook 的 组件故事格式 (CSF) 是一种新兴的组件示例标准,在 Storybook 5.2 中引入。CSF 文件是没有任何 Storybook 特定扩展的 ES6 模块。因此,它们简单、便携且面向未来。

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

编写 CSF 组件很容易。您在想要显示的那个状态下实例化您的组件,将其包装在一个函数中,然后为其命名。

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

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

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 原则:Don't Repeat Yourself(不要重复自己)。但在实践中,我们在组件中看到了大量的重复。

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

// 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 组件比以前更具可移植性。

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

随着 CSF 在生态系统中的普及,这个前景正在成为现实。它内置于热门的 React 框架 RedwoodJS 中。它支持 webcomponents.dev,一个强大的在线 IDE。并且,在撰写本文时,它已被至少一个流行的设计工具采用。

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 风格的功能!我们将在后续的帖子中介绍自动生成的控件actions时,更详细地解释这一点。

移除这些 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' };

瞧!您已经编写了第一个 Args 组件。但这只是冰山一角。Args 还为插件注入了强大的功能,并为 Storybook 带来了许多新功能,我们将在未来几周内详细介绍。

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

  • 🎛 自动生成的 控件,用于组件属性,
  • 📢 自动生成的 actions,用于事件日志记录,
  • 🛠 自定义工具栏,用于主题、国际化和上下文。

参与进来

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,468开发者及更多

我们正在招聘!

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

查看职位

热门帖子

Storybook Controls

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

零配置 Storybook

简单设置,即时提高生产力
loading
Michael Shilman

Storybook 5.3

更快地构建生产级设计系统
loading
Michael Shilman
加入社区
7,468开发者及更多
原因为什么选择 Storybook组件驱动的 UI
文档指南教程更新日志遥测
社区插件参与进来博客
展示探索项目组件词汇表
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI