Storybook 是业界标准的 UI 组件工作台,用于在隔离环境中构建、测试和文档化组件。借助 Storybook 8,团队可以独立于业务逻辑和后端依赖开发 UI 组件,使用 play 函数运行交互测试,通过 Chromatic 捕获视觉回归,并自动生成活文档。无论使用 React、Vue、Angular 还是 Svelte,Storybook 都提供统一的组件驱动开发环境。
Storybook 8 是一个 UI 组件工作台,支持在隔离环境中构建、测试和文档化组件。它支持 React、Vue、Angular 和 Svelte,采用 CSF3 故事格式、args 和 controls 交互属性、play 函数交互测试、Chromatic 视觉回归、a11y 插件无障碍审计、MDX 文档和强大的插件生态。
- Storybook 8 使用 CSF3 格式,基于对象语法,比之前的格式更简洁、类型安全且可组合。
- Args 和 Controls 生成交互式面板,设计师和 QA 可以实时操作组件属性而无需编辑代码。
- Play 函数允许直接在故事中编写交互测试,使用 Testing Library 模拟用户点击、输入和断言。
- Chromatic 集成通过跨提交、浏览器和视口比较截图来捕获视觉回归。
- a11y 插件基于 axe-core 规则对每个故事运行自动化无障碍审计,提前发现 WCAG 违规。
- Storybook 从故事和 MDX 文件生成活文档,确保文档始终与实际组件代码同步。
什么是 Storybook 和组件驱动开发?
Storybook 是一个开源工具,提供沙盒环境用于在隔离状态下开发 UI 组件。你可以定义故事来渲染具有不同属性、状态和上下文的组件,而不需要浏览整个应用程序。
组件驱动开发(CDD)是一种自下而上构建 UI 的方法论,从原子组件开始,组合成分子组件,最终组装成有机体和页面。Storybook 是支持此工作流的主要工具。
Storybook 与替代方案对比
Storybook 在组件工作台领域占主导地位,但也存在替代方案。Ladle 是基于 Vite 的轻量级 React 替代方案。Histoire 支持 Vue 和 Svelte。然而 Storybook 拥有最大的生态系统(400+ 插件)、最多的框架支持和最强的 CI/CD 集成。
Feature Comparison: Storybook vs Alternatives
┌──────────────────┬────────────┬────────┬──────────┬──────────────┐
│ Feature │ Storybook │ Ladle │ Histoire │ React Cosmos │
├──────────────────┼────────────┼────────┼──────────┼──────────────┤
│ React │ Yes │ Yes │ No │ Yes │
│ Vue │ Yes │ No │ Yes │ No │
│ Angular │ Yes │ No │ No │ No │
│ Svelte │ Yes │ No │ Yes │ No │
│ Addons │ 400+ │ ~10 │ ~20 │ ~15 │
│ Visual Testing │ Chromatic │ Manual │ Manual │ Manual │
│ Play Functions │ Yes │ No │ No │ No │
│ MDX Docs │ Yes │ No │ Yes │ No │
│ CI Test Runner │ Yes │ No │ No │ No │
│ Build Tool │ Vite │ Vite │ Vite │ Webpack │
└──────────────────┴────────────┴────────┴──────────┴──────────────┘为 React、Vue、Angular 和 Svelte 设置 Storybook
Storybook 8 提供零配置设置体验。init 命令自动检测框架、安装依赖并生成配置文件。Storybook 8 默认使用 Vite 构建,提供更快的启动和热更新。
React 设置
# Initialize Storybook in a React project
npx storybook@latest init
# Or create a new React project with Storybook
npx create-react-app my-app --template typescript
cd my-app
npx storybook@latest initVue 设置
# Initialize Storybook in a Vue 3 project
npm create vue@latest my-vue-app
cd my-vue-app
npx storybook@latest initAngular 设置
# Initialize Storybook in an Angular project
ng new my-angular-app
cd my-angular-app
npx storybook@latest initSvelte 设置
# Initialize Storybook in a SvelteKit project
npx sv create my-svelte-app
cd my-svelte-app
npx storybook@latest init初始化后,Storybook 创建 .storybook 目录,包含 main.ts(构建配置)和 preview.ts(运行时配置)。
// .storybook/main.ts
import type { StorybookConfig } from "@storybook/react-vite";
const config: StorybookConfig = {
stories: [
"../src/**/*.mdx",
"../src/**/*.stories.@(js|jsx|mjs|ts|tsx)",
],
addons: [
"@storybook/addon-onboarding",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
"@storybook/addon-a11y",
],
framework: {
name: "@storybook/react-vite",
options: {},
},
};
export default config;
// .storybook/preview.ts
import type { Preview } from "@storybook/react";
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)/i,
date: /Date$/i,
},
},
},
};
export default preview;使用 CSF3 格式编写故事
CSF3 是 Storybook 8 中的最新故事格式。故事定义为普通对象而非函数,更简洁且可组合。每个文件有一个默认导出(meta)描述组件,命名导出定义各个故事。
CSF3 引入了故事级 args,自动生成 Controls UI,允许非开发人员交互式探索组件变体。
// src/components/Button.stories.ts
import type { Meta, StoryObj } from "@storybook/react";
import { Button } from "./Button";
// Meta describes the component
const meta = {
title: "Components/Button",
component: Button,
tags: ["autodocs"],
argTypes: {
variant: {
control: "select",
options: ["primary", "secondary", "danger"],
},
size: {
control: "radio",
options: ["sm", "md", "lg"],
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
// Each named export is a story
export const Primary: Story = {
args: {
variant: "primary",
size: "md",
children: "Click me",
},
};
export const Secondary: Story = {
args: {
...Primary.args,
variant: "secondary",
},
};
export const Danger: Story = {
args: {
...Primary.args,
variant: "danger",
children: "Delete",
},
};
export const Large: Story = {
args: {
...Primary.args,
size: "lg",
children: "Large Button",
},
};Args、Controls 和 ArgTypes
Args 是在 Storybook 中定义和操作组件属性的机制。定义 args 后,Storybook 自动生成 Controls 面板。ArgTypes 提供关于 args 的元数据,可自定义控件类型、设置有效选项和定义默认值。
Controls 支持多种输入类型:文本、数字、布尔值、单选、下拉、颜色选择器、日期选择器、滑块和对象编辑器。
// src/components/Card.stories.ts
import type { Meta, StoryObj } from "@storybook/react";
import { Card } from "./Card";
const meta = {
title: "Components/Card",
component: Card,
argTypes: {
title: { control: "text", description: "Card heading" },
description: { control: "text" },
imageUrl: { control: "text" },
variant: {
control: "select",
options: ["default", "elevated", "outlined"],
table: {
defaultValue: { summary: "default" },
},
},
isLoading: { control: "boolean" },
rating: {
control: { type: "range", min: 0, max: 5, step: 0.5 },
},
backgroundColor: { control: "color" },
publishedAt: { control: "date" },
tags: { control: "object" },
onClick: { action: "clicked" },
},
} satisfies Meta<typeof Card>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
title: "Getting Started with Storybook",
description: "Learn how to build components in isolation.",
variant: "default",
isLoading: false,
rating: 4.5,
tags: ["tutorial", "react"],
},
};装饰器和参数
装饰器用额外的渲染逻辑包装故事,可提供主题、路由、国际化等上下文。装饰器可在故事级、组件级或全局级应用。
参数是附加到故事的静态元数据,用于配置插件行为,如视口大小、背景色和布局模式。
// .storybook/preview.tsx - Global decorators
import type { Preview } from "@storybook/react";
import { ThemeProvider } from "../src/theme";
import { MemoryRouter } from "react-router-dom";
import "../src/globals.css";
const preview: Preview = {
decorators: [
// Wrap all stories with ThemeProvider
(Story) => (
<ThemeProvider>
<Story />
</ThemeProvider>
),
// Wrap all stories with Router
(Story) => (
<MemoryRouter initialEntries={["/"]}>
<Story />
</MemoryRouter>
),
],
parameters: {
layout: "centered",
backgrounds: {
default: "light",
values: [
{ name: "light", value: "#ffffff" },
{ name: "dark", value: "#1a1a2e" },
{ name: "gray", value: "#f5f5f5" },
],
},
viewport: {
viewports: {
mobile: { name: "Mobile", styles: { width: "375px", height: "667px" } },
tablet: { name: "Tablet", styles: { width: "768px", height: "1024px" } },
desktop: { name: "Desktop", styles: { width: "1440px", height: "900px" } },
},
},
},
};
export default preview;Play 函数交互测试
Play 函数是 Storybook 8 最强大的功能之一。可以使用 Testing Library 直接在故事中编写交互测试。故事加载时自动执行交互并在面板中报告结果。
Play 函数在浏览器中运行,测试实际 DOM 行为。可模拟点击、键盘输入、表单提交等。配合 test-runner 可在 CI 中自动执行。
// src/components/LoginForm.stories.ts
import type { Meta, StoryObj } from "@storybook/react";
import { within, userEvent, expect } from "@storybook/test";
import { LoginForm } from "./LoginForm";
const meta = {
title: "Forms/LoginForm",
component: LoginForm,
} satisfies Meta<typeof LoginForm>;
export default meta;
type Story = StoryObj<typeof meta>;
export const FilledForm: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Type into the email field
await userEvent.type(
canvas.getByLabelText("Email"),
"user@example.com"
);
// Type into the password field
await userEvent.type(
canvas.getByLabelText("Password"),
"securePassword123"
);
// Click the submit button
await userEvent.click(
canvas.getByRole("button", { name: /sign in/i })
);
// Assert the success message appears
await expect(
canvas.getByText("Welcome back!")
).toBeInTheDocument();
},
};
export const ValidationError: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Submit without filling fields
await userEvent.click(
canvas.getByRole("button", { name: /sign in/i })
);
// Assert validation errors
await expect(
canvas.getByText("Email is required")
).toBeInTheDocument();
},
};使用 Chromatic 进行视觉回归测试
视觉回归测试通过跨提交比较组件截图来捕获意外的视觉变化。Chromatic 是 Storybook 团队构建的官方云服务。
当 Chromatic 检测到视觉变化时,会创建审查流程。审查者可批准或拒绝变更,该流程与 GitHub PR 集成。
# Install Chromatic
npm install --save-dev chromatic
# Run visual tests (first time creates baselines)
npx chromatic --project-token=YOUR_TOKEN
# In CI (GitHub Actions example)
# .github/workflows/chromatic.yml
name: Chromatic
on: push
jobs:
chromatic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
- run: npm ci
- uses: chromaui/action@latest
with:
projectToken: \${{ secrets.CHROMATIC_PROJECT_TOKEN }}
# Enable TurboSnap for faster builds
onlyChanged: true使用 a11y 插件进行无障碍测试
a11y 插件将 axe-core 无障碍测试集成到 Storybook 中,自动对每个故事运行审计并报告违规。
可按故事或全局配置严重级别和规则覆盖。配合 play 函数可测试交互状态下的无障碍性。
# Install the a11y addon
npm install --save-dev @storybook/addon-a11y
// .storybook/main.ts - Add to addons
addons: [
"@storybook/addon-essentials",
"@storybook/addon-a11y",
],
// Configure a11y rules per story
export const DarkMode: Story = {
args: { theme: "dark" },
parameters: {
a11y: {
config: {
rules: [
// Disable a specific rule for this story
{ id: "color-contrast", enabled: false },
],
},
},
},
};
// Combine a11y with play function
export const DropdownOpen: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
// Open dropdown so a11y addon audits the open state
await userEvent.click(canvas.getByRole("button"));
await expect(canvas.getByRole("listbox")).toBeVisible();
},
};使用 MDX 编写文档
Storybook 支持 MDX 编写富文档页面。MDX 文档可嵌入实时故事预览、组件属性表、代码片段和自定义组件。
Storybook 8 默认使用 autodocs,自动从故事和 JSDoc 注释生成文档页面。也可用自定义 MDX 文件覆盖。
// src/components/Button.mdx
import { Meta, Story, Canvas, Controls } from "@storybook/blocks";
import * as ButtonStories from "./Button.stories";
<Meta of={ButtonStories} />
# Button
The Button component is the primary interactive element
in our design system. Use it for actions and navigation.
## Usage Guidelines
- Use **Primary** for the main call to action
- Use **Secondary** for supporting actions
- Use **Danger** only for destructive actions
## Interactive Demo
<Canvas of={ButtonStories.Primary} />
<Controls of={ButtonStories.Primary} />
## All Variants
<Canvas>
<Story of={ButtonStories.Primary} />
<Story of={ButtonStories.Secondary} />
<Story of={ButtonStories.Danger} />
</Canvas>插件生态系统
Storybook 拥有最大的插件生态系统,超过 400 个社区插件。官方插件包括 Controls、Actions、Viewport、Backgrounds 等。
插件通过定义良好的 API 接入 Storybook 生命周期,可添加面板、工具栏按钮、装饰器和自定义标签。
# Essential addons (included by default)
npm install @storybook/addon-essentials
# Includes: Controls, Actions, Viewport, Backgrounds,
# Toolbars, Measure, Outline, Docs
# Popular community addons
npm install storybook-dark-mode # Dark mode toggle
npm install @storybook/addon-designs # Figma embeds
npm install msw-storybook-addon # API mocking with MSW
npm install storybook-addon-performance # Performance profiling
npm install @storybook/addon-coverage # Code coverage
// .storybook/main.ts
const config: StorybookConfig = {
addons: [
"@storybook/addon-essentials",
"@storybook/addon-a11y",
"@storybook/addon-interactions",
"storybook-dark-mode",
"@storybook/addon-designs",
],
};// Using MSW addon to mock API calls in stories
import { http, HttpResponse } from "msw";
export const WithData: Story = {
parameters: {
msw: {
handlers: [
http.get("/api/users", () => {
return HttpResponse.json([
{ id: 1, name: "Alice", role: "admin" },
{ id: 2, name: "Bob", role: "user" },
]);
}),
],
},
},
};
export const WithError: Story = {
parameters: {
msw: {
handlers: [
http.get("/api/users", () => {
return HttpResponse.json(
{ error: "Internal Server Error" },
{ status: 500 }
);
}),
],
},
},
};TypeScript 集成
Storybook 8 具有一流的 TypeScript 支持。Meta 和 StoryObj 类型为故事提供完整的类型检查。TypeScript 属性类型自动提取以生成 ArgTypes 和 Controls。
TypeScript 的 satisfies 操作符与 CSF3 配合良好,提供类型检查同时允许 Storybook 推断每个故事的具体 args。
// Full TypeScript example with strict typing
import type { Meta, StoryObj } from "@storybook/react";
import { DataTable, type DataTableProps } from "./DataTable";
type User = {
id: number;
name: string;
email: string;
role: "admin" | "editor" | "viewer";
};
// satisfies provides type checking without widening
const meta = {
title: "Data/DataTable",
component: DataTable<User>,
tags: ["autodocs"],
argTypes: {
sortDirection: {
control: "radio",
options: ["asc", "desc"] as const,
},
onRowClick: { action: "row-clicked" },
},
} satisfies Meta<DataTableProps<User>>;
export default meta;
type Story = StoryObj<typeof meta>;
const sampleUsers: User[] = [
{ id: 1, name: "Alice", email: "alice@co.com", role: "admin" },
{ id: 2, name: "Bob", email: "bob@co.com", role: "editor" },
];
export const Default: Story = {
args: {
data: sampleUsers,
columns: ["name", "email", "role"],
sortable: true,
sortDirection: "asc",
},
};CI/CD 集成
Storybook 通过 test-runner 包和 Chromatic 集成到 CI 流程。test-runner 在无头浏览器中执行所有 play 函数。
典型的 CI 流程:运行 test-runner 进行交互测试,发布到 Chromatic 进行视觉回归测试,部署构建后的 Storybook 供评审。
# Install the test runner
npm install --save-dev @storybook/test-runner
# Run interaction tests locally
npx test-storybook
# Run with coverage
npx test-storybook --coverage
# package.json scripts
"scripts": {
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"test-storybook": "test-storybook",
"test-storybook:ci": "concurrently -k -s first \
\"npx http-server storybook-static -p 6006\" \
\"npx wait-on tcp:6006 && test-storybook\""
}
# GitHub Actions workflow
# .github/workflows/storybook.yml
name: Storybook Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npm run build-storybook
- run: npm run test-storybook:ci
- uses: chromaui/action@latest
with:
projectToken: \${{ secrets.CHROMATIC_TOKEN }}性能优化与最佳实践
随着组件库增长,Storybook 构建和启动时间可能增加。以下最佳实践帮助保持工作流高效。
- 使用 Vite 构建器(Storybook 8 默认)获得更快的启动和 HMR。
- 启用懒编译,只编译导航到的故事,减少初始启动时间。
- 每个组件一个故事文件,与组件文件放在一起便于发现。
- 使用 args 而非硬编码属性,使每个变体可通过 Controls 探索。
- 在尽可能低的层级应用装饰器,避免切换故事时不必要的重渲染。
- 使用 play 函数进行交互测试,保持测试靠近故事。
- 配置 autodocs 自动生成文档,而非手动维护 MDX 文件。
- 使用装饰器和 MSW 模拟外部依赖,而非真实后端。
- 使用组合功能在 monorepo 中合并多个 Storybook。
- 设置 Chromatic TurboSnap 只捕获受代码变更影响的故事快照,减少 CI 时间和成本。
// Recommended project structure
src/
components/
Button/
Button.tsx # Component
Button.stories.ts # Stories (co-located)
Button.test.ts # Unit tests
Button.mdx # Custom docs (optional)
Button.module.css # Styles
index.ts # Barrel export
Card/
Card.tsx
Card.stories.ts
index.ts
.storybook/
main.ts # Build config
preview.ts # Runtime config
manager.ts # UI customization常见问题
Storybook 用来做什么?
Storybook 用于在隔离环境中开发、测试和文档化 UI 组件。团队用它进行组件驱动开发、设计系统维护、视觉回归测试、无障碍审计和生成活文档。
什么是 Storybook 中的 CSF3?
CSF3 是 Storybook 8 中的最新故事格式。故事定义为带有 args 属性的普通对象,更简洁、可组合且类型安全。
Storybook 中的 play 函数如何工作?
Play 函数是附加到故事的异步函数,使用 Testing Library 模拟用户交互。故事加载时自动执行,test-runner 可在 CI 中执行。
Storybook 和 Chromatic 有什么区别?
Storybook 是开源组件工作台,Chromatic 是付费云服务,提供视觉回归测试和协作审查工作流。
Storybook 支持 Vue 和 Angular 吗?
支持。Storybook 8 支持 React、Vue 3、Angular、Svelte、Web Components 等。
如何在 Storybook 中测试无障碍性?
安装 a11y 插件,它集成 axe-core 自动运行无障碍审计并在面板中显示违规。
可以从 Storybook 生成文档吗?
可以。Storybook 8 的 autodocs 自动从故事和类型生成文档页面,也支持自定义 MDX 文档。
如何将 Storybook 集成到 CI/CD?
使用 test-runner 在 CI 中执行 play 函数,集成 Chromatic 进行视觉回归测试,部署静态 Storybook 供评审。