多个组件的故事
如果这些组件被设计为一起工作,编写可以渲染两个或多个组件的故事非常有用。例如,ButtonGroup
、List
和 Page
组件。
子组件
当您正在记录的组件具有父子关系时,您可以使用 subcomponents
属性将它们一起记录。当子组件不打算单独使用,而仅作为父组件的一部分使用时,这尤其有用。
以下是一个使用 List
和 ListItem
组件的示例
import React from 'react';
import type { Meta, StoryObj } from '@storybook/react';
import { List } from './List';
import { ListItem } from './ListItem';
const meta: Meta<typeof List> = {
component: List,
subcomponents: { ListItem }, //👈 Adds the ListItem component as a subcomponent
};
export default meta;
type Story = StoryObj<typeof List>;
export const Empty: Story = {};
export const OneItem: Story = {
render: (args) => (
<List {...args}>
<ListItem />
</List>
),
};
请注意,通过向默认导出添加 subcomponents
属性,我们在 ArgTypes 和 Controls 表格中获得了一个额外的面板,其中列出了 ListItem
的 props
子组件仅用于文档目的,并且有一些限制
- 子组件的 argTypes 是推断的(对于支持该功能的渲染器),不能手动定义或覆盖。
- 每个已记录子组件的表格 *不* 包括 controls 来更改 props 的值,因为 controls 始终应用于主组件的参数。
让我们讨论一些您可以用来缓解上述问题的技巧,这些技巧在更复杂的情况下尤其有用。
重用故事定义
我们还可以通过重用故事定义来减少故事中的重复。在这里,我们可以重用 ListItem
故事的参数在 List
的故事中。
import type { Meta, StoryObj } from '@storybook/react';
import { List } from './List';
import { ListItem } from './ListItem';
//👇 We're importing the necessary stories from ListItem
import { Selected, Unselected } from './ListItem.stories';
const meta: Meta<typeof List> = {
component: List,
};
export default meta;
type Story = StoryObj<typeof List>;
export const ManyItems: Story = {
render: (args) => (
<List {...args}>
<ListItem {...Selected.args} />
<ListItem {...Unselected.args} />
<ListItem {...Unselected.args} />
</List>
),
};
通过使用其参数渲染 Unchecked
故事,我们能够在 List
中重用来自 ListItem
故事的输入数据。
但是,我们仍然没有使用参数来控制 ListItem
故事,这意味着我们无法使用控件更改它们,也无法在其他更复杂的组件故事中重用它们。
使用子元素作为参数
我们改进这种情况的一种方法是将渲染的子组件提取到 children
参数中
import type { Meta, StoryObj } from '@storybook/react';
import { List } from './List';
//👇 Instead of importing ListItem, we import the stories
import { Unchecked } from './ListItem.stories';
const meta: Meta<typeof List> = {
/* 👇 The title prop is optional.
* See https://storybook.org.cn/docs/configure/#configure-story-loading
* to learn how to generate automatic titles
*/
title: 'List',
component: List,
};
export default meta;
type Story = StoryObj<typeof List>;
export const OneItem: Story = {
args: {
children: <Unchecked {...Unchecked.args} />,
},
};
现在 children
是一个参数,我们可以在另一个故事中重用它。
但是,使用此方法时有一些注意事项,您应该注意。
children
参数,就像所有参数一样,需要是 JSON 可序列化的。为避免 Storybook 出现错误,您应该
我们目前正在努力改进 children 参数的整体体验,并允许您在控件中编辑 children 参数,并允许您在不久的将来使用其他类型的组件。但目前,在实现故事时需要考虑此注意事项。
创建模板组件
另一种更“数据”导向的方法是创建一个特殊的“故事生成”模板组件
import type { Meta, StoryObj } from '@storybook/react';
import { List } from './List';
import { ListItem } from './ListItem';
//👇 Imports a specific story from ListItem stories
import { Unchecked } from './ListItem.stories';
const meta: Meta<typeof List> = {
/* 👇 The title prop is optional.
* Seehttps://storybook.org.cn/docs/configure/#configure-story-loading
* to learn how to generate automatic titles
*/
title: 'List',
component: List,
};
export default meta;
type Story = StoryObj<typeof List>;
//👇 The ListTemplate construct will be spread to the existing stories.
const ListTemplate: Story = {
render: ({ items, ...args }) => {
return (
<List>
{items.map((item) => (
<ListItem {...item} />
))}
</List>
);
},
};
export const Empty = {
...ListTemplate,
args: {
items: [],
},
};
export const OneItem = {
...ListTemplate,
args: {
items: [{ ...Unchecked.args }],
},
};
这种方法的设置稍微复杂一些,但意味着您可以更轻松地重用组合组件中每个故事的args
。这也意味着您可以使用 Controls 插件更改组件的 args。