
介绍 Storybook Args
下一代动态组件示例

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 将互操作性推向了一个新的水平,通过从您的组件中移除插件依赖。考虑以下使用actions和knobs的 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 Langen、Filipp Riabchun、Atanas Stoyanov 和 Leo Y. Li。
Storybook 由 1000 多名开源贡献者维护,并由顶级维护者指导委员会领导。如果您有兴趣贡献,请在 GitHub 上查看 Storybook,创建问题或提交拉取请求。在 Open Collective 上捐赠。在 Discord 上与我们聊天 - 通常有维护者在线。