storybook-story-writing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStorybook - Story Writing
Storybook - 故事编写
Write well-structured, maintainable Storybook stories using Component Story Format 3 (CSF3) that showcase component variations and ensure consistent rendering.
使用Component Story Format 3(CSF3)编写结构清晰、易于维护的Storybook故事,展示组件的不同变体并确保渲染一致性。
Key Concepts
核心概念
Component Story Format 3 (CSF3)
Component Story Format 3 (CSF3)
CSF3 is the modern Storybook format that uses object syntax for stories:
typescript
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',
},
};CSF3是Storybook的现代格式,使用对象语法来编写故事:
typescript
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',
},
};Story Organization
故事组织
- One story file per component:
Component.stories.tsx - Use descriptive story names: ,
Primary,Secondary,LargeDisabled - Group related stories under a title hierarchy:
Components/Forms/Input
- 每个组件对应一个故事文件:
Component.stories.tsx - 使用描述性的故事名称:、
Primary、Secondary、LargeDisabled - 将相关故事按标题层级分组:
Components/Forms/Input
Default Export (Meta)
默认导出(Meta)
The default export defines metadata for all stories:
typescript
const meta = {
title: 'Components/Button', // Navigation path
component: Button, // Component reference
parameters: {}, // Story-level config
tags: ['autodocs'], // Enable auto-documentation
argTypes: {}, // Control types
decorators: [], // Wrappers for stories
} satisfies Meta<typeof Button>;默认导出定义了所有故事的元数据:
typescript
const meta = {
title: 'Components/Button', // 导航路径
component: Button, // 组件引用
parameters: {}, // 故事级配置
tags: ['autodocs'], // 启用自动文档
argTypes: {}, // 控件类型
decorators: [], // 故事包装器
} satisfies Meta<typeof Button>;Best Practices
最佳实践
1. Use TypeScript for Type Safety
1. 使用TypeScript保证类型安全
typescript
import type { Meta, StoryObj } from '@storybook/react';
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
type Story = StoryObj<typeof meta>;typescript
import type { Meta, StoryObj } from '@storybook/react';
const meta = {
component: Button,
} satisfies Meta<typeof Button>;
type Story = StoryObj<typeof meta>;2. Show All Component States
2. 展示组件的所有状态
Create stories for each meaningful state:
typescript
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',
},
};为每个有意义的状态创建故事:
typescript
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. Use Sensible Defaults
3. 使用合理的默认值
typescript
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
size: 'medium',
},
};
// Extend existing stories
export const PrimaryLarge: Story = {
...Primary,
args: {
...Primary.args,
size: 'large',
},
};typescript
export const Primary: Story = {
args: {
primary: true,
label: 'Button',
size: 'medium',
},
};
// 扩展现有故事
export const PrimaryLarge: Story = {
...Primary,
args: {
...Primary.args,
size: 'large',
},
};4. Add Descriptive Parameters
4. 添加描述性参数
typescript
export const WithTooltip: Story = {
args: {
label: 'Hover me',
tooltip: 'Click to submit',
},
parameters: {
docs: {
description: {
story: 'Shows a tooltip on hover to provide additional context.',
},
},
},
};typescript
export const WithTooltip: Story = {
args: {
label: 'Hover me',
tooltip: 'Click to submit',
},
parameters: {
docs: {
description: {
story: 'Shows a tooltip on hover to provide additional context.',
},
},
},
};5. Use Decorators for Context
5. 使用装饰器提供上下文
typescript
import { RouterDecorator } from '../decorators';
const meta = {
component: Navigation,
decorators: [
(Story) => (
<div style={{ padding: '3rem' }}>
<Story />
</div>
),
RouterDecorator,
],
} satisfies Meta<typeof Navigation>;typescript
import { RouterDecorator } from '../decorators';
const meta = {
component: Navigation,
decorators: [
(Story) => (
<div style={{ padding: '3rem' }}>
<Story />
</div>
),
RouterDecorator,
],
} satisfies Meta<typeof Navigation>;Common Patterns
常见模式
Form Components
表单组件
typescript
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: 'Invalid email format',
name: 'Name is required',
},
},
};typescript
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: 'Invalid email format',
name: 'Name is required',
},
},
};Layout Components
布局组件
typescript
export const WithSidebar: Story = {
args: {
sidebar: <Sidebar items={sidebarItems} />,
children: <Content />,
},
parameters: {
layout: 'fullscreen',
},
};typescript
export const WithSidebar: Story = {
args: {
sidebar: <Sidebar items={sidebarItems} />,
children: <Content />,
},
parameters: {
layout: 'fullscreen',
},
};Data-Driven Components
数据驱动组件
typescript
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: 'No items found',
},
};typescript
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: 'No items found',
},
};Responsive Components
响应式组件
typescript
export const Mobile: Story = {
args: {
variant: 'mobile',
},
parameters: {
viewport: {
defaultViewport: 'mobile1',
},
},
};
export const Desktop: Story = {
args: {
variant: 'desktop',
},
parameters: {
viewport: {
defaultViewport: 'desktop',
},
},
};typescript
export const Mobile: Story = {
args: {
variant: 'mobile',
},
parameters: {
viewport: {
defaultViewport: 'mobile1',
},
},
};
export const Desktop: Story = {
args: {
variant: 'desktop',
},
parameters: {
viewport: {
defaultViewport: 'desktop',
},
},
};Anti-Patterns
反模式
❌ Don't Use Template Binding (CSF2)
❌ 不要使用模板绑定(CSF2)
typescript
// Bad - Old CSF2 format
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = { label: 'Button' };typescript
// Good - CSF3 format
export const Primary: Story = {
args: { label: 'Button' },
};typescript
// Bad - Old CSF2 format
const Template = (args) => <Button {...args} />;
export const Primary = Template.bind({});
Primary.args = { label: 'Button' };typescript
// Good - CSF3 format
export const Primary: Story = {
args: { label: 'Button' },
};❌ Don't Mix Logic in Stories
❌ 不要在故事中混入逻辑
typescript
// Bad
export const Complex: Story = {
render: (args) => {
const [state, setState] = useState(false);
useEffect(() => {
// Complex side effects
}, []);
return <Component {...args} />;
},
};typescript
// Good - Move logic to component or use play functions
export const Complex: Story = {
args: { initialState: false },
};typescript
// Bad
export const Complex: Story = {
render: (args) => {
const [state, setState] = useState(false);
useEffect(() => {
// Complex side effects
}, []);
return <Component {...args} />;
},
};typescript
// Good - Move logic to component or use play functions
export const Complex: Story = {
args: { initialState: false },
};❌ Don't Hardcode Repetitive Props
❌ 不要硬编码重复的属性
typescript
// Bad
export const Story1: Story = {
args: { label: 'Button', size: 'medium', theme: 'light' },
};
export const Story2: Story = {
args: { label: 'Submit', size: 'medium', theme: 'light' },
};typescript
// Good - Use meta-level defaults
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' },
};typescript
// Bad
export const Story1: Story = {
args: { label: 'Button', size: 'medium', theme: 'light' },
};
export const Story2: Story = {
args: { label: 'Submit', size: 'medium', theme: 'light' },
};typescript
// Good - Use meta-level defaults
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' },
};❌ Don't Skip Story Types
❌ 不要省略故事类型
typescript
// Bad - Missing type annotation
export const Primary = {
args: { label: 'Button' },
};typescript
// Good - With type
export const Primary: Story = {
args: { label: 'Button' },
};typescript
// Bad - Missing type annotation
export const Primary = {
args: { label: 'Button' },
};typescript
// Good - With type
export const Primary: Story = {
args: { label: 'Button' },
};Related Skills
相关技能
- storybook-args-controls: Advanced arg configuration and interactive controls
- storybook-play-functions: Automated interaction testing within stories
- storybook-component-documentation: Auto-generating component documentation
- storybook-args-controls:高级参数配置与交互式控件
- storybook-play-functions:故事内的自动化交互测试
- storybook-component-documentation:自动生成组件文档