Storybook故事编写Skill storybook-story-writing

这个技能用于创建和维护 Storybook 故事,遵循 Component Story Format 3 (CSF3),确保组件故事结构化、可维护,并正确展示组件变体。它适用于前端开发,提高组件文档和测试效率。关键词:Storybook、CSF3、前端开发、组件故事、类型安全、最佳实践、自动化测试。

前端开发 0 次安装 0 次浏览 更新于 3/25/2026

名称: storybook-story-writing 用户可调用: false 描述: 用于创建或修改组件的 Storybook 故事。确保故事遵循 CSF3 格式,正确展示组件变体,并成功构建。 允许工具:

  • 读取
  • 写入
  • 编辑
  • Bash
  • Grep
  • Glob

Storybook - 故事编写

编写结构良好、可维护的 Storybook 故事,使用 Component Story Format 3 (CSF3),展示组件变体并确保一致渲染。

关键概念

Component Story Format 3 (CSF3)

CSF3 是现代 Storybook 格式,使用对象语法编写故事:

import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta = {
  title: 'Components/Button',
  component: Button,
  parameters: {
    layout: 'centered',
  },
  tags: ['autodocs'],
  argTypes: {
    backgroundColor: { control: 'color' },
  },
} satisfies Meta<typeof Button>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Primary: Story = {
  args: {
    primary: true,
    label: 'Button',
  },
};

export const Secondary: Story = {
  args: {
    label: 'Button',
  },
};

故事组织

  • 每个组件一个故事文件: Component.stories.tsx
  • 使用描述性故事名称: Primary, Secondary, Large, Disabled
  • 在标题层次下分组相关故事: Components/Forms/Input

默认导出 (Meta)

默认导出定义所有故事的元数据:

const meta = {
  title: 'Components/Button',        // 导航路径
  component: Button,                  // 组件引用
  parameters: {},                     // 故事级配置
  tags: ['autodocs'],                // 启用自动文档
  argTypes: {},                       // 控件类型
  decorators: [],                     // 故事包装器
} satisfies Meta<typeof Button>;

最佳实践

1. 使用 TypeScript 确保类型安全

import type { Meta, StoryObj } from '@storybook/react';

const meta = {
  component: Button,
} satisfies Meta<typeof Button>;

type Story = StoryObj<typeof meta>;

2. 展示所有组件状态

为每个有意义的狀態创建故事:

export const Default: Story = {
  args: {
    label: 'Click me',
  },
};

export const Loading: Story = {
  args: {
    label: 'Loading...',
    loading: true,
  },
};

export const Disabled: Story = {
  args: {
    label: 'Disabled',
    disabled: true,
  },
};

export const WithIcon: Story = {
  args: {
    label: 'Download',
    icon: 'download',
  },
};

3. 使用合理的默认值

export const Primary: Story = {
  args: {
    primary: true,
    label: 'Button',
    size: 'medium',
  },
};

// 扩展现有故事
export const PrimaryLarge: Story = {
  ...Primary,
  args: {
    ...Primary.args,
    size: 'large',
  },
};

4. 添加描述性参数

export const WithTooltip: Story = {
  args: {
    label: 'Hover me',
    tooltip: 'Click to submit',
  },
  parameters: {
    docs: {
      description: {
        story: '悬停时显示工具提示以提供额外上下文。',
      },
    },
  },
};

5. 使用装饰器提供上下文

import { RouterDecorator } from '../decorators';

const meta = {
  component: Navigation,
  decorators: [
    (Story) => (
      <div style={{ padding: '3rem' }}>
        <Story />
      </div>
    ),
    RouterDecorator,
  ],
} satisfies Meta<typeof Navigation>;

常见模式

表单组件

export const EmptyForm: Story = {
  args: {
    onSubmit: (data) => console.log(data),
  },
};

export const PrefilledForm: Story = {
  args: {
    defaultValues: {
      email: 'user@example.com',
      name: 'John Doe',
    },
  },
};

export const WithValidationErrors: Story = {
  args: {
    errors: {
      email: '无效的邮箱格式',
      name: '姓名是必填项',
    },
  },
};

布局组件

export const WithSidebar: Story = {
  args: {
    sidebar: <Sidebar items={sidebarItems} />,
    children: <Content />,
  },
  parameters: {
    layout: 'fullscreen',
  },
};

数据驱动组件

const mockData = [
  { id: 1, name: 'Item 1' },
  { id: 2, name: 'Item 2' },
  { id: 3, name: 'Item 3' },
];

export const WithData: Story = {
  args: {
    items: mockData,
  },
};

export const Empty: Story = {
  args: {
    items: [],
    emptyMessage: '未找到项目',
  },
};

响应式组件

export const Mobile: Story = {
  args: {
    variant: 'mobile',
  },
  parameters: {
    viewport: {
      defaultViewport: 'mobile1',
    },
  },
};

export const Desktop: Story = {
  args: {
    variant: 'desktop',
  },
  parameters: {
    viewport: {
      defaultViewport: 'desktop',
    },
  },
};

反模式

❌ 不要使用模板绑定 (CSF2)

// 坏 - 旧 CSF2 格式
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = { label: 'Button' };
// 好 - CSF3 格式
export const Primary: Story = {
  args: { label: 'Button' },
};

❌ 不要在故事中混合逻辑

// 坏
export const Complex: Story = {
  render: (args) => {
    const [state, setState] = useState(false);
    useEffect(() => {
      // 复杂的副作用
    }, []);
    return <Component {...args} />;
  },
};
// 好 - 将逻辑移到组件或使用 play 函数
export const Complex: Story = {
  args: { initialState: false },
};

❌ 不要硬编码重复属性

// 坏
export const Story1: Story = {
  args: { label: 'Button', size: 'medium', theme: 'light' },
};
export const Story2: Story = {
  args: { label: 'Submit', size: 'medium', theme: 'light' },
};
// 好 - 使用元级默认值
const meta = {
  component: Button,
  args: {
    size: 'medium',
    theme: 'light',
  },
} satisfies Meta<typeof Button>;

export const Story1: Story = {
  args: { label: 'Button' },
};
export const Story2: Story = {
  args: { label: 'Submit' },
};

❌ 不要跳过故事类型

// 坏 - 缺少类型注释
export const Primary = {
  args: { label: 'Button' },
};
// 好 - 带类型
export const Primary: Story = {
  args: { label: 'Button' },
};

相关技能

  • storybook-args-controls: 高级参数配置和交互控件
  • storybook-play-functions: 故事内自动交互测试
  • storybook-component-documentation: 自动生成组件文档