
引入 Storybook Args
下一代动态组件示例

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 通过从您的故事中移除插件依赖性,将互操作性向前推进了一步。考虑以下使用 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 式的功能!在我们后续的博文中介绍自动生成的控件和动作时,我们将详细解释这是如何工作的。
移除这些 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 Langen、Filipp Riabchun、Atanas Stoyanov 和 Leo Y. Li。
Storybook 由 1000 多名开源贡献者维护,并由顶尖维护者组成的指导委员会指导。如果您有兴趣贡献,请在 GitHub 上查看 Storybook,创建议题,或提交拉取请求。在 Open Collective 上捐赠。在 Discord 中与我们聊天 — 通常有维护者在线。