多个组件的故事
如果组件设计为协同工作,编写能够同时渲染两个或多个组件的故事会很有用。例如,ButtonGroup
、List
和 Page
组件。
子组件
当您正在文档化的组件之间存在父子关系时,您可以使用 subcomponents
属性将它们一起文档化。这在子组件不单独使用,而仅作为父组件的一部分使用时尤其有用。
这是一个使用 List
和 ListItem
组件的示例
import 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 值的controls,因为 controls 始终应用于主组件的 args。
接下来,我们将讨论一些可以用来弥补上述限制的技术,这些技术在更复杂的情况下尤其有用。
重用故事定义
我们还可以通过重用故事定义来减少故事中的重复。在这里,我们可以在 List
的故事中重用 ListItem
故事的 args。
// 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
故事,我们能够在 List
中重用 ListItem
故事的输入数据。
然而,我们仍然没有使用 args 来控制 ListItem
故事,这意味着我们无法使用 controls 来更改它们,也无法在其他更复杂的组件故事中重用它们。
将 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';
//👇 Instead of importing ListItem, we import the stories
import { Unchecked } from './ListItem.stories';
const meta = {
/* 👇 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,
} satisfies Meta<typeof List>;
export default meta;
type Story = StoryObj<typeof meta>;
export const OneItem: Story = {
args: {
children: <Unchecked {...Unchecked.args} />,
},
};
现在 children
是一个 arg,我们可以在另一个故事中潜在地重用它。
然而,使用这种方法时有一些需要注意的警告。
children
arg 与所有 args 一样,需要是 JSON 可序列化的。为了避免 Storybook 出错,您应该
我们目前正在努力改进 children arg 的整体体验,未来将允许您在 control 中编辑 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 = {
/* 👇 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,
} 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
。这也意味着您可以使用 Controls 插件更改组件的 args。