Storybook 组件文档:UI 组件隔离开发与展示
为什么需要 Storybook?——UI 组件隔离开发与展示
当项目越来越大,组件数量急剧增长,你可能会遇到这些痛点:
- 想查看某个按钮在「主要/次要/禁用/加载中」状态的样子,必须反复切换代码或手动触发状态。
- 组件与页面强耦合,单独调试一个组件就需要启动整个应用。
- 设计师或产品经理想验收组件,却没有一个可以即时查看的可视化目录。
- 新同事接手项目,面对几十个组件不知从何看起,缺少一份“活着的”组件使用手册。
Storybook 正是为了解决这些问题而生。 它是一个前端组件开发环境与文档生成工具,能够在项目之外独立运行,让你可以:
- 在隔离环境中开发、调试 UI 组件。
- 为每个组件编写多个“故事”(Story),覆盖各种状态与交互。
- 自动生成可交互的组件文档,供团队共享。
- 与视觉回归测试、单元测试、无障碍检查工具集成。
本教程将带你从零开始,掌握 Storybook 的安装、故事编写、文档生成、插件配置与部署,最终打造一套专业、可维护的组件库文档体系。
环境准备与安装
前置条件
- 已有基于 React、Vue、Angular、Svelte 等现代框架的项目(或能创建新项目)。
- Node.js ≥ 16(推荐 18+),包管理器使用 npm、yarn 或 pnpm。
在现有项目中安装 Storybook
在项目根目录执行以下命令,Storybook 会自动识别你的框架并安装依赖:
npx storybook@latest init
该命令会完成:
- 检测项目所使用的框架(React、Vue 等)。
- 安装
@storybook/react(或对应框架包)及必要依赖。 - 创建默认配置文件(
.storybook/main.js)和一个示例 Story。 - 在
package.json中添加storybook和build-storybook脚本。
安装完成后,启动 Storybook:
npm run storybook
浏览器会打开 http://localhost:6006,你将看到内置的示例组件及其文档。
项目结构概览
初始化后的关键目录与文件:
.storybook/
main.js # 主配置:故事路径、插件、Webpack/Vite 配置
preview.js # 全局参数、装饰器、全局样式
src/
stories/ # 示例故事文件(可自定义路径)
编写你的第一个 Story
Story 就是一个函数,返回组件在特定状态下的渲染结果。Storybook 通过“故事”来描述组件的各种表现。
组件文件 (以 React 为例)
假设我们有一个简单的 Button 组件:
// src/components/Button.jsx
export const Button = ({ label, primary, disabled, size, onClick }) => {
const mode = primary ? 'btn-primary' : 'btn-secondary';
return (
<button
className={`btn ${mode} btn-${size}`}
disabled={disabled}
onClick={onClick}
>
{label}
</button>
);
};
创建 Story 文件
在组件同级目录或 stories 目录下创建 Button.stories.jsx:
import { Button } from './Button';
export default {
title: '通用/Button',
component: Button,
// 定义组件参数,会与控件面板联动
argTypes: {
label: { control: 'text' },
primary: { control: 'boolean' },
size: {
control: 'select',
options: ['small', 'medium', 'large'],
},
disabled: { control: 'boolean' },
},
};
// 模板:返回组件渲染结果
const Template = (args) => <Button {...args} />;
// 默认状态故事
export const Default = Template.bind({});
Default.args = {
label: '按钮',
primary: false,
size: 'medium',
disabled: false,
};
// 主要按钮
export const Primary = Template.bind({});
Primary.args = {
...Default.args,
primary: true,
};
// 禁用态
export const Disabled = Template.bind({});
Disabled.args = {
...Default.args,
disabled: true,
};
保存后,Storybook 侧边栏立即出现“通用/Button”分组,下方是 Default、Primary、Disabled 三个故事。点击即可在右侧 Canvas 面板预览,并在 Controls 面板动态修改属性。
Story 格式详解:CSF 3.0 声明式写法
Storybook 7 开始推荐 CSF 3.0(Component Story Format)写法,更加简洁与声明式。上面的例子可以改写为:
export default {
title: '通用/Button',
component: Button,
};
export const Default = {
args: {
label: '按钮',
primary: false,
size: 'medium',
disabled: false,
},
};
export const Primary = {
args: {
...Default.args,
primary: true,
},
};
CSF 3.0 不再需要 Template.bind({}),每一个故事就是一个对象,args 直接挂载在故事对象上。当你需要使用多个模板时,可以借助 render 函数。
何时使用 render?
当不同故事需要不同渲染逻辑(例如不同标签、容器装饰)时,可以为单个故事定义 render:
export const WithIcon = {
args: { label: '搜索', icon: 'search' },
render: (args) => (
<div style={{ background: '#333', padding: 20 }}>
<Button {...args} />
</div>
),
};
交互式故事:play 函数与用户操作模拟
借助 @storybook/addon-interactions,你可以为故事添加自动化交互操作,像编写测试一样验证组件行为。
安装交互插件
npm install @storybook/addon-interactions --save-dev
在 .storybook/main.js 中添加:
export default {
addons: ['@storybook/addon-interactions'],
};
为故事添加 play 函数
play 函数接收一个包含 canvasElement 的对象,可使用 Testing Library 的工具在 Canvas 中执行交互:
import { userEvent, within } from '@storybook/testing-library';
export const ClickInteraction = {
args: { label: '点击我' },
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = await canvas.getByRole('button');
await userEvent.click(button);
// 断言:按钮点击后移除了自身(示例)
expect(button).not.toBeInTheDocument();
},
};
执行 play 函数时,交互步骤会记录在 Interactions 面板中,你可以逐步回放,直观地调试。
用 MDX 制作组件文档页
Storybook 支持 MDX,允许你将 Markdown 文档与实时渲染的 Story 编织在一起。
1. 安装 MDX 支持(通常默认支持)
确保 .storybook/main.js 中有 '@storybook/addon-docs'。
2. 创建 MDX 文档文件
在组件旁新建 Button.mdx:
import { Meta, Story, Canvas, ArgTypes } from '@storybook/blocks';
import { Button } from './Button';
<Meta title="通用/Button" component={Button} />
# Button 按钮
按钮用于触发即时操作。我们提供了多种样式和状态。
## 基本按钮
<Canvas>
<Story name="基本" args={{ label: '按钮' }}>
{(args) => <Button {...args} />}
</Story>
</Canvas>
## 属性
<ArgTypes story="基本" />
MDX 页面会自动出现在侧边栏“通用/Button”下,点击后既能看到说明文档,又能直接与组件交互。
控件类型与参数扩展
通过 argTypes 你可以精细定义每个属性在 Controls 面板的表现形式、描述、分类等。
常用 Control 类型:
| 类型 | 说明 |
|---|---|
text |
文本输入框 |
boolean |
切换开关 |
number |
数字输入,可配合 min、max、step |
select |
下拉选择,options 定义可选项 |
radio / inline-radio |
单选按钮组 |
check / inline-check |
多选按钮组 |
color |
颜色拾取器 |
date |
日期选择器 |
object / array |
JSON 编辑器 |
示例:让 size 属性以单选按钮展示,并添加描述与表格分类:
export default {
title: '通用/Button',
component: Button,
argTypes: {
size: {
control: 'radio',
options: ['small', 'medium', 'large'],
description: '按钮尺寸',
table: {
category: '样式',
defaultValue: { summary: 'medium' },
},
},
},
};
装饰器与全局配置
全局装饰器(为每个故事包裹外层)
在 .storybook/preview.js 中可以为所有故事应用全局样式、路由、状态容器等:
import { ThemeProvider } from '../src/ThemeContext';
import '../src/index.css'; // 全局样式
export const decorators = [
(Story) => (
<ThemeProvider defaultTheme="light">
<Story />
</ThemeProvider>
),
];
组件/故事级装饰器
在 meta 或单一 Story 中也可单独使用 decorators:
export default {
title: '通用/Button',
component: Button,
decorators: [
(Story) => (
<div style={{ margin: '3em' }}>
<Story />
</div>
),
],
};
插件生态:扩展 Storybook 的能力
Storybook 的强大来自其插件体系。以下为核心必装插件:
| 插件包名 | 功能 |
|---|---|
@storybook/addon-essentials |
包含 Controls、Actions、Viewport、Backgrounds、Toolbars、Outline 等 |
@storybook/addon-a11y |
无障碍检查 |
@storybook/addon-links |
故事间导航链接 |
@storybook/addon-interactions |
交互测试与回放 |
@storybook/addon-mdx-gfm |
MDX 中使用 GitHub 风格 Markdown |
@storybook/addon-designs |
在故事中嵌入 Figma 设计稿 |
@storybook/addon-storyshots / @storybook/test-runner |
截图与快照测试 |
安装示例:
npm install @storybook/addon-a11y --save-dev
在 .storybook/main.js 中注册:
module.exports = {
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
'@storybook/addon-interactions',
],
};
安装后,工具栏会多出“Accessibility”按钮,检测颜色对比度、ARIA 属性等。
自动化测试与持续集成
交互与断言测试
通过 play 函数配合 expect,故事本身就可以成为测试用例。执行测试时,使用 Storybook Test Runner:
npm install @storybook/test-runner --save-dev
运行:
npx test-storybook
它会启动 Storybook,按顺序执行每个故事的 play 函数,并报告结果。
视觉回归测试
借助 Chromatic(Storybook 官方视觉测试服务)或 Percy,你可以自动截取每个故事并与上次构建比较,发现像素级差异。
简单上手 Chromatic:
-
注册 Chromatic 并获取项目 token。
-
安装依赖:
npm install chromatic --save-dev -
运行:
npx chromatic --project-token=你的token
第一次上传作为基线,之后每次 PR 变化都会产生快照对比。
与不同框架的集成要点
Storybook 官方支持 React、Vue、Angular、Svelte、Web Components 等。约定如下:
-
React:可直接使用 JSX,组件 props 即 args。
-
Vue:需要绑定 props 与 events;事件名使用
on前缀(如onClick映射click事件)。CSF 举例:export default { title: 'Example/MyVueButton', component: MyButton, argTypes: { onClick: { action: 'clicked' }, }, }; export const Default = { render: (args) => ({ components: { MyButton }, setup() { return { args }; }, template: '<MyButton v-bind="args" />', }), }; -
Angular:使用
component自动从 Angular 组件提取inputs与outputs,直接传args即可。 -
Svelte、Web Components 类似,主要差异在于 args 与事件绑定的写法。
构建与部署 Storybook 静态站点
完成所有故事编写后,将其构建为纯静态文件,即可部署到任何静态托管平台。
构建命令:
npm run build-storybook
输出目录默认为 storybook-static。你可以直接通过 npx http-server storybook-static 本地预览。
常用部署目标:
- GitHub Pages:将
storybook-static推送到gh-pages分支,或在 CI 中使用actions-gh-pages。 - Netlify / Vercel:指定构建命令
build-storybook和发布目录storybook-static。 - 企业内部服务器:直接放置静态文件。
示例 npm 脚本一键部署到 GitHub Pages(需先安装 gh-pages):
"deploy-storybook": "build-storybook && gh-pages -d storybook-static"
最佳实践与常见问题
组织 Story 的命名规则
- 使用路径式分组:
title: '组件/Button'、'组件/表单/Input',自动生成折叠侧边栏。 - 子组件可以放在同一 Story 文件中,使用
subcomponents属性(需插件支持)或独立 story 并互相链接。
保持 Story 纯净
- 每个 Story 应该只渲染一个组件,避免无意中引入复杂的页面布局。
- 使用装饰器提供所需上下文(路由、主题)。
- 如果需要展示多个相关组件(如
ListItem在List内),用decorators包裹。
动态参数与复杂数据
对于数组、对象参数,在 args 中直接设置,并在 control: 'object' 下可编辑。若数据量很大,可以提供一个固定数据集的故事,并通过 Controls 快速验证不同情况。
处理样式问题
- 确保在
preview.js中导入全局 CSS、字体文件等。 - 如果使用了 Tailwind,请确保在
.storybook/preview.js中引入其样式文件。 - 遇到组件渲染空白页?在
.storybook/main.js中检查stories路径匹配是否正确。
避免 Story 过多导致性能下降
- 大量 Story 会增加启动和构建时间。可以按需编写核心状态的故事,或使用 MDX 将变体集中展示。
- 使用
play函数注意异步等待,避免个别 Story 阻塞整个测试。
总结
通过本教程你已经掌握了:
- 搭建 Storybook 环境,编写第一个组件故事。
- 使用 CSF 3.0 声明式定义故事,并结合 Controls 动态调节 props。
- 编写交互式 play 函数,让故事成为可执行的行为测试。
- 利用 MDX 制作整合文档与实时代码的组件页面。
- 配置核心插件,实现无障碍检查、设计稿嵌入、视觉回归测试等高级功能。
- 构建静态文档站点并部署到云端。
Storybook 让 UI 组件开发脱离了业务的束缚,成为真正可复用、可展示、可测试的独立单元。从今天起,尝试为你项目的每个关键组件都写一个 Story,把活文档带给整个团队。