Storybook 是组件开发的 IDE:在隔离环境里写组件 + 各种状态展示 +
自动文档 + 视觉回归测试。Storybook 8 升级了 Vite-first / 性能大涨。
安装
npx storybook@latest init
# 自动检测项目类型(React/Vue/Svelte 等),生成 .storybook/ 配置
package.json 加 script:
{
"scripts": {
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build"
}
}
第一个 story
// src/components/Button.tsx
export function Button({ children, variant = 'primary', onClick }) {
return <button className={`btn btn-${variant}`} onClick={onClick}>{children}</button>
}
// src/components/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'
const meta: Meta<typeof Button> = {
title: 'Components/Button',
component: Button,
tags: ['autodocs'], // 自动生成文档页
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
},
onClick: { action: 'clicked' },
},
}
export default meta
type Story = StoryObj<typeof Button>
export const Primary: Story = {
args: { children: 'Click me', variant: 'primary' },
}
export const Secondary: Story = {
args: { children: 'Secondary', variant: 'secondary' },
}
export const Danger: Story = {
args: { children: 'Delete', variant: 'danger' },
}
npm run storybook → 浏览器看到 Button 的 3 个变体 + interactive controls
可以实时改 args。
2. Controls
argTypes: {
size: {
control: { type: 'range', min: 12, max: 32, step: 2 },
},
bg: { control: 'color' },
date: { control: 'date' },
}
Storybook UI 自动渲染滑块 / 颜色选择器 / 日期选择器。
3. 自动文档
tags: ['autodocs'] 让 Storybook 自动生成 Docs 页:
- 组件描述(取自 JSDoc / TypeScript types)
- Props 表格(取自 TypeScript types)
- 所有 story 实例展示
4. MDX 写富文档
{/* src/components/Button.mdx */}
import { Canvas, Story } from '@storybook/blocks'
import * as ButtonStories from './Button.stories'
# Button
按钮组件。
## 何时使用
- 提交表单
- 触发关键操作
## 示例
<Canvas of={ButtonStories.Primary} />
注意:danger 按钮配合二次确认 modal 使用。
<Canvas of={ButtonStories.Danger} />
MDX 让你混 Markdown + 实际可交互的 story。
5. play 函数:交互测试
import { userEvent, within, expect } from '@storybook/test'
export const ClickHandling: Story = {
args: { children: 'Click me' },
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement)
const btn = canvas.getByRole('button')
await userEvent.click(btn)
expect(args.onClick).toHaveBeenCalled()
},
}
打开这个 story Storybook 自动执行 play 函数 → 模拟点击 → 校验。
相当于把单元测试 + 视觉展示合一。
可以直接 npm run test-storybook 在 CI 里跑所有 play 函数。
6. decorators:包一层 context
const meta: Meta = {
decorators: [
(Story) => (
<ThemeProvider theme="light">
<div style={{ padding: 24 }}>
<Story />
</div>
</ThemeProvider>
),
],
}
每个 story 自动套 Provider + 内边距。
7. globalTypes:主题 / 语言切换器
// .storybook/preview.ts
export const globalTypes = {
theme: {
description: '主题',
defaultValue: 'light',
toolbar: {
title: 'Theme',
icon: 'circlehollow',
items: ['light', 'dark'],
},
},
}
export const decorators = [
(Story, ctx) => (
<ThemeProvider theme={ctx.globals.theme}>
<Story />
</ThemeProvider>
),
]
Storybook 顶部工具栏出现切换按钮,所有 story 同步主题。
8. addon:a11y / viewport / measure
npm i -D @storybook/addon-a11y @storybook/addon-viewport
.storybook/main.ts:
export default {
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y', // axe-core 自动 a11y 检查
'@storybook/addon-viewport', // 不同设备尺寸
],
}
每个 story 自动跑 a11y audit;右上角设备切换看响应式。
9. 视觉回归测试(Chromatic)
npx chromatic --project-token=xxx
Chromatic(Storybook 母公司服务)每次 build 自动截图所有 story,
和上次对比,任何视觉变化提醒你确认。
替代方案:自托管 reg-suit / loki / Percy。
10. 部署 storybook
npm run build-storybook
# 输出到 storybook-static/
# 上传任何静态托管(Vercel / Netlify / GitHub Pages / S3)
设计师 / PM / 客户能直接看组件演示。
11. 与 design token
把 Figma design tokens 导出 JSON → 写 Tailwind / Styled-Components 主题,
在 Storybook 里切换主题对比效果。Tokens Studio + Figma + Storybook 是
设计系统的成熟工作流。
12. 何时不用 Storybook
- 小项目(< 10 个组件):维护 stories 文件成本 > 收益
- 业务页面(不是可复用组件):直接在 app 里开发更快
- 团队不愿意写 stories:勉强引入只会废弃
适合:组件库开发 / 设计系统 / 跨团队共享组件场景。
踩过的坑
- Story 文件忘了 default export meta:Storybook 不识别。
- 全局样式没在 preview.ts 里 import:story 看到的样式和 app 不一致。
.storybook/preview.ts里import '../src/index.css'。 - Storybook 8 + Vite 5 升级有 breaking change:
framework: '@storybook/react-vite'
必须明确写。 - Chromatic 视觉测试对小动画 / 字体渲染差异敏感,可能 false positive。
配delay: 200等动画结束再截图。
登录后参与评论。