多个组件的 Stories
编写一次渲染两个或多个组件的故事非常有用,前提是这些组件被设计为协同工作。例如,ButtonGroup、List 和 Page 组件。
子组件
当您要记录的组件具有父子关系时,您可以使用subcomponents属性将它们一起记录。当子组件不打算单独使用,而仅作为父组件的一部分使用时,这一点尤其有用。
这是一个使用 List 和 ListItem 组件的示例
import * as React from 'react';
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { List } from './List';
import { ListItem } from './ListItem';
const meta = {
component: List,
subcomponents: { ListItem }, //👈 Adds the ListItem component as a subcomponent
} satisfies Meta<typeof List>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Empty: Story = {};
export const OneItem: Story = {
render: (args) => (
<List {...args}>
<ListItem />
</List>
),
};请注意,通过向默认导出添加 subcomponents 属性,我们在ArgTypes 和 Controls 表中获得了一个额外的面板,其中列出了 ListItem 的 props

子组件仅用于文档目的,并且有一些限制
- 子组件的argTypes是推断的(对于支持此功能的渲染器),不能手动定义或覆盖。
- 为每个记录的子组件提供的表格不包含控件来更改 props 的值,因为控件始终应用于主组件的 args。
让我们讨论一些您可以用来缓解上述问题的技术,这些技术在更复杂的情况下特别有用。
重用故事定义
我们还可以通过重用故事定义来减少故事中的重复。在这里,我们可以重用 ListItem 故事的 args 在 List 的故事中
import * as React from 'react';
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { List } from './List';
import { ListItem } from './ListItem';
//👇 We're importing the necessary stories from ListItem
import { Selected, Unselected } from './ListItem.stories';
const meta = {
component: List,
} satisfies Meta<typeof List>;
export default meta;
type Story = StoryObj<typeof meta>;
export const ManyItems: Story = {
render: (args) => (
<List {...args}>
<ListItem {...Selected.args} />
<ListItem {...Unselected.args} />
<ListItem {...Unselected.args} />
</List>
),
};通过渲染带有其 args 的 Unchecked 故事,我们可以重用 ListItem 故事中的输入数据到 List 中。
但是,我们仍然没有使用 args 来控制 ListItem 故事,这意味着我们无法通过 controls 更改它们,也无法在其他更复杂的组件故事中重用它们。
将子组件用作 arg
改进这种情况的一种方法是将渲染出的子组件提取到一个 children arg 中
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { List } from './List';
//👇 Instead of importing ListItem, we import the stories
import { Unchecked } from './ListItem.stories';
const meta = {
component: List,
} satisfies Meta<typeof List>;
export default meta;
type Story = StoryObj<typeof meta>;
export const OneItem: Story = {
args: {
children: <Unchecked {...Unchecked.args} />,
},
};现在 children 是一个 arg,我们可以在另一个故事中潜在地重用它。
但是,使用这种方法有一些需要注意的细节。
与所有 arg 一样,children arg 需要是 JSON 可序列化的。为避免 Storybook 出错,您应该
我们目前正在改进 children arg 的整体体验,并允许您在不久的将来在控件中编辑 children arg,并允许您使用其他类型的组件。但目前,您在实现您的故事时需要考虑这个注意事项。
创建模板组件
另一个更“基于数据”的选项是创建一个特殊的“故事生成”模板组件
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { List } from './List';
import { ListItem } from './ListItem';
//👇 Imports a specific story from ListItem stories
import { Unchecked } from './ListItem.stories';
const meta = {
component: List,
} satisfies Meta<typeof List>;
export default meta;
type Story = StoryObj<typeof meta>;
//👇 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。它也意味着您可以通过控件面板来修改组件的 args。
