文件
Storybook 文件

多個組件的故事

如果這些組件的設計目的是協同工作,那麼同時渲染兩個或多個組件的 story 會很有用。例如,ButtonGroupListPage 組件。

子組件

當您正在撰寫文件的組件具有父子關係時,您可以使用 subcomponents 屬性將它們一起記錄。當子組件不應單獨使用,而僅作為父組件的一部分時,這特別有用。

以下是一個 ListListItem 組件的範例

List.stories.ts|tsx
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 屬性新增至預設匯出,我們會在 ArgTypesControls 表格上獲得額外的面板,列出 ListItem 的 props

Subcomponents in ArgTypes doc block

子組件僅適用於文件目的,且有一些限制

  1. 子組件的 argTypes推斷 (對於支援該功能的渲染器),且無法手動定義或覆寫。
  2. 每個已記錄子組件的表格都包含可變更 props 值的控制項,因為控制項始終會套用至主要組件的 args。

讓我們來談談一些可以用來減輕上述情況的技巧,這些技巧在更複雜的情況下特別有用。

重複使用 story 定義

我們也可以透過重複使用 story 定義來減少 story 中的重複。在這裡,我們可以在 List 的 story 中重複使用 ListItem stories 的 args

List.stories.ts|tsx
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>
  ),
};

透過使用其 args 渲染 Unchecked story,我們能夠在 List 中重複使用 ListItem stories 的輸入資料。

但是,我們仍然沒有使用 args 來控制 ListItem stories,這表示我們無法使用控制項變更它們,也無法在其他更複雜的組件 story 中重複使用它們。

將 children 作為 arg 使用

我們改善這種情況的一種方法是將渲染的子組件提取到 children arg 中

List.stories.ts|tsx
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.dev.org.tw/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 是一個 arg,我們就可以在另一個 story 中重複使用它。

但是,當您使用這種方法時,有一些注意事項應該要注意。

與所有 args 一樣,children arg 需要可序列化為 JSON。為了避免您的 Storybook 發生錯誤,您應該

  • 避免使用空值
  • 如果您想要使用控制項調整值,請使用映射
  • 謹慎使用包含第三方程式庫的組件

我們目前正在努力改善 children arg 的整體體驗,並讓您在控制項中編輯 children arg,並讓您在不久的將來使用其他類型的組件。但是現在,您在實作您的 story 時需要考慮這個注意事項。

建立範本組件

另一個更「以資料」為基礎的選項是建立一個特殊的「story 產生」範本組件

List.stories.ts|tsx
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.dev.org.tw/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 }],
  },
};

此方法的設定比較複雜,但這表示您可以更輕鬆地在複合組件中重複使用每個 story 的 args。這也表示您可以使用控制項外掛變更組件的 args。