多个组件的故事
如果这些组件设计为协同工作,那么一次编写 渲染两个或更多组件 的故事很有用。例如,ButtonGroup
、List
和 Page
组件。
¥It's useful to write stories that render two or more components at once if those components are designed to work together. For example, ButtonGroup
, List
, and Page
components.
子组件
¥Subcomponents
当你记录的组件具有父子关系时,你可以使用 subcomponents
属性将它们一起记录。当子组件不打算单独使用,而仅作为父组件的一部分使用时,这尤其有用。
¥When the components you're documenting have a parent-child relationship, you can use the subcomponents
property to document them together. This is especially useful when the child component is not meant to be used on its own, but only as part of the parent component.
这是一个包含 List
和 ListItem
组件的示例:
¥Here's an example with List
and ListItem
components:
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 和 控件 表上获得一个额外的面板,列出 ListItem
的属性:
¥Note that by adding a subcomponents
property to the default export, we get an extra panel on the ArgTypes and Controls tables, listing the props of ListItem
:
子组件仅用于文档目的,并且有一些限制:
¥Subcomponents are only intended for documentation purposes and have some limitations:
-
子组件的 argTypes 是 推断(对于支持该功能的渲染器),不能手动定义或覆盖。
¥The argTypes of subcomponents are inferred (for the renderers that support that feature) and cannot be manually defined or overridden.
-
每个记录的子组件的表格不包括 controls 来更改 props 的值,因为控件始终适用于主组件的参数。
¥The table for each documented subcomponent does not include controls to change the value of the props, because controls always apply to the main component's args.
让我们讨论一些可以用来缓解上述问题的技术,这些技术在更复杂的情况下特别有用。
¥Let's talk about some techniques you can use to mitigate the above, which are especially useful in more complicated situations.
重复使用故事定义
¥Reusing story definitions
我们还可以通过重用故事定义来减少故事中的重复。在这里,我们可以在 List
的故事中重用 ListItem
故事的参数:
¥We can also reduce repetition in our stories by reusing story definitions. Here, we can reuse the ListItem
stories' args in the story for 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
故事的输入数据。
¥By rendering the Unchecked
story with its args, we are able to reuse the input data from the ListItem
stories in the List
.
但是,我们仍然没有使用参数来控制 ListItem
故事,这意味着我们无法使用控件更改它们,也无法在其他更复杂的组件故事中重用它们。
¥However, we still aren’t using args to control the ListItem
stories, which means we cannot change them with controls and we cannot reuse them in other, more complex component stories.
使用 children 作为参数
¥Using children as an arg
我们改善这种情况的一种方法是将渲染的子组件拉出到 children
参数中:
¥One way we improve that situation is by pulling the rendered subcomponent out into a children
arg:
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.js.org/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
是一个参数,我们可以在另一个故事中重复使用它。
¥Now that children
is an arg, we can potentially reuse it in another story.
但是,使用此方法时有一些注意事项需要注意。
¥However, there are some caveats when using this approach that you should be aware of.
children
arg 与所有 arg 一样,需要 JSON 可序列化。为了避免 Storybook 出现错误,你应该:
¥The children
arg, just like all args, needs to be JSON serializable. To avoid errors with your Storybook, you should:
-
避免使用空值
¥Avoid using empty values
-
谨慎使用包含第三方库的组件
¥Use caution with components that include third party libraries
我们目前正在努力改善子参数的整体体验,并允许你在控件中编辑子参数,并允许你在不久的将来使用其他类型的组件。但是现在,你在实现故事时需要考虑这个警告。
¥We're currently working on improving the overall experience for the children arg and allow you to edit children arg in a control and allow you to use other types of components in the near future. But for now you need to factor in this caveat when you're implementing your stories.
创建模板组件
¥Creating a Template Component
另一种更基于“数据”的选项是创建一个特殊的“故事生成”模板组件:
¥Another option that is more “data”-based is to create a special “story-generating” template component:
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.js.org/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 插件更改组件的参数。
¥This approach is a little more complex to setup, but it means you can more easily reuse the args
to each story in a composite component. It also means that you can alter the args to the component with the Controls addon.