名称: 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: 自动生成组件文档