插件系统 #
插件概述 #
Storybook 的插件系统是其最强大的特性之一,通过插件可以扩展 Storybook 的功能,提升组件开发体验。
text
┌─────────────────────────────────────────────────────────────┐
│ Storybook 插件生态 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 官方插件 │
│ ├── @storybook/addon-essentials 核心插件集合 │
│ ├── @storybook/addon-interactions 交互测试 │
│ ├── @storybook/addon-a11y 无障碍检查 │
│ └── @storybook/addon-coverage 代码覆盖率 │
│ │
│ 社区插件 │
│ ├── storybook-dark-mode 暗色模式 │
│ ├── @storybook/addon-designs 设计稿集成 │
│ └── storybook-addon-pseudo-states 伪类状态 │
│ │
│ 自定义插件 │
│ └── 根据需求开发专属插件 │
│ │
└─────────────────────────────────────────────────────────────┘
安装插件 #
基本安装 #
bash
# 安装单个插件
npm install --save-dev @storybook/addon-a11y
# 安装多个插件
npm install --save-dev @storybook/addon-a11y @storybook/addon-coverage
配置插件 #
javascript
// .storybook/main.js
export default {
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
'@storybook/addon-a11y',
],
};
官方核心插件 #
@storybook/addon-essentials #
核心插件集合,包含最常用的插件:
javascript
// .storybook/main.js
export default {
addons: ['@storybook/addon-essentials'],
};
包含的插件:
text
┌─────────────────────────────────────────────────────────────┐
│ Essentials 包含的插件 │
├─────────────────────────────────────────────────────────────┤
│ │
│ Docs 自动生成组件文档 │
│ Controls 动态参数控制面板 │
│ Actions 事件日志记录 │
│ Viewport 视口切换 │
│ Backgrounds 背景颜色切换 │
│ Measure 元素测量 │
│ Outline 元素轮廓显示 │
│ │
└─────────────────────────────────────────────────────────────┘
Docs 插件 #
自动生成组件文档:
javascript
// 启用自动文档
export default {
title: 'Components/Button',
component: Button,
tags: ['autodocs'],
};
// 自定义文档
export default {
parameters: {
docs: {
description: {
component: `
## Button 组件
按钮用于触发操作。
### 使用场景
- 表单提交
- 页面导航
`,
},
source: {
type: 'code',
},
},
},
};
Controls 插件 #
动态控制组件参数:
javascript
export default {
argTypes: {
variant: {
control: 'select',
options: ['primary', 'secondary', 'danger'],
description: '按钮变体',
},
size: {
control: 'radio',
options: ['small', 'medium', 'large'],
},
disabled: {
control: 'boolean',
},
},
};
Actions 插件 #
记录事件日志:
javascript
// .storybook/preview.js
export default {
parameters: {
actions: {
// 自动捕获以 on 开头的函数
argTypesRegex: '^on[A-Z].*',
},
},
};
// 手动使用
import { action } from '@storybook/addon-actions';
export const WithClick = {
args: {
onClick: action('button-clicked'),
},
};
Viewport 插件 #
切换设备视口:
javascript
export default {
parameters: {
viewport: {
viewports: {
iPhone12: {
name: 'iPhone 12',
styles: {
width: '390px',
height: '844px',
},
},
iPadPro: {
name: 'iPad Pro',
styles: {
width: '1024px',
height: '1366px',
},
},
},
defaultViewport: 'iPhone12',
},
},
};
Backgrounds 插件 #
切换背景颜色:
javascript
export default {
parameters: {
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#1a1a1a' },
{ name: 'brand', value: '#1890ff' },
{ name: 'twitter', value: '#00aced' },
],
},
},
};
官方扩展插件 #
@storybook/addon-interactions #
交互测试插件:
bash
npm install --save-dev @storybook/addon-interactions @storybook/test
javascript
// .storybook/main.js
export default {
addons: ['@storybook/addon-interactions'],
};
javascript
// 使用 play 函数
import { within, userEvent, expect } from '@storybook/test';
export const InteractiveButton = {
args: {
onClick: fn(),
},
play: async ({ args, canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button');
await userEvent.click(button);
await expect(args.onClick).toHaveBeenCalled();
},
};
@storybook/addon-a11y #
无障碍检查插件:
bash
npm install --save-dev @storybook/addon-a11y
javascript
// .storybook/main.js
export default {
addons: ['@storybook/addon-a11y'],
};
javascript
// 配置规则
export default {
parameters: {
a11y: {
element: '#root',
config: {},
options: {},
manual: false,
},
},
};
@storybook/addon-coverage #
代码覆盖率插件:
bash
npm install --save-dev @storybook/addon-coverage
javascript
// .storybook/main.js
export default {
addons: ['@storybook/addon-coverage'],
};
@storybook/addon-links #
Story 间链接:
javascript
import { linkTo } from '@storybook/addon-links';
export const WithLink = {
args: {
onClick: linkTo('Components/Button', 'Primary'),
},
};
// MDX 中使用
import { LinkTo } from '@storybook/addon-links';
<LinkTo story="primary">查看 Primary 按钮</LinkTo>
社区插件 #
storybook-dark-mode #
暗色模式切换:
bash
npm install --save-dev storybook-dark-mode
javascript
// .storybook/main.js
export default {
addons: ['storybook-dark-mode'],
};
javascript
// .storybook/preview.js
export default {
parameters: {
darkMode: {
dark: { ...themes.dark, appBg: 'black' },
light: { ...themes.normal, appBg: 'white' },
},
},
};
@storybook/addon-designs #
设计稿集成:
bash
npm install --save-dev @storybook/addon-designs
javascript
// .storybook/main.js
export default {
addons: ['@storybook/addon-designs'],
};
javascript
export default {
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/...',
},
},
};
storybook-addon-pseudo-states #
伪类状态预览:
bash
npm install --save-dev storybook-addon-pseudo-states
javascript
// .storybook/main.js
export default {
addons: ['storybook-addon-pseudo-states'],
};
javascript
export const Hover = {
parameters: {
pseudo: { hover: true },
},
};
export const Focus = {
parameters: {
pseudo: { focus: true },
},
};
export const Active = {
parameters: {
pseudo: { active: true },
},
};
@storybook/addon-storysource #
显示源码:
bash
npm install --save-dev @storybook/addon-storysource
javascript
// .storybook/main.js
export default {
addons: ['@storybook/addon-storysource'],
};
storybook-addon-rtl #
RTL 支持:
bash
npm install --save-dev storybook-addon-rtl
javascript
// .storybook/main.js
export default {
addons: ['storybook-addon-rtl'],
};
插件配置 #
全局配置 #
javascript
// .storybook/preview.js
export default {
parameters: {
// 全局背景配置
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#333333' },
],
},
// 全局视口配置
viewport: {
defaultViewport: 'responsive',
},
// 全局控件配置
controls: {
expanded: true,
sort: 'requiredFirst',
},
// 全局文档配置
docs: {
toc: true,
},
},
};
Story 级配置 #
javascript
export const CustomBackground = {
parameters: {
backgrounds: { default: 'dark' },
},
};
export const CustomViewport = {
parameters: {
viewport: { defaultViewport: 'mobile1' },
},
};
禁用插件 #
javascript
// 全局禁用
export default {
parameters: {
controls: { disable: true },
actions: { disable: true },
},
};
// Story 级别禁用
export const NoControls = {
parameters: {
controls: { disable: true },
},
};
自定义插件开发 #
插件结构 #
text
my-addon/
├── src/
│ ├── register.js # UI 注册
│ ├── preset.js # 预设配置
│ └── Panel.js # 面板组件
├── package.json
└── README.md
注册插件 #
javascript
// src/register.js
import { addons, types } from '@storybook/manager-api';
import { AddonPanel } from '@storybook/components';
import MyPanel from './Panel';
const ADDON_ID = 'my-addon';
const PANEL_ID = `${ADDON_ID}/panel`;
addons.register(ADDON_ID, (api) => {
addons.add(PANEL_ID, {
type: types.PANEL,
title: 'My Addon',
render: ({ active, key }) => (
<AddonPanel active={active} key={key}>
<MyPanel api={api} />
</AddonPanel>
),
});
});
面板组件 #
javascript
// src/Panel.js
import React, { useState } from 'react';
import { useParameter } from '@storybook/api';
import { Form } from '@storybook/components';
export const MyPanel = () => {
const [value, setValue] = useState('');
const params = useParameter('myParameter', {});
return (
<div style={{ padding: 20 }}>
<Form.Textarea
value={value}
onChange={(e) => setValue(e.target.value)}
placeholder="输入内容..."
/>
<pre>{JSON.stringify(params, null, 2)}</pre>
</div>
);
};
预设配置 #
javascript
// src/preset.js
module.exports = {
config: (config) => ({
...config,
// 添加 webpack 配置
}),
managerEntries: (entries = []) => [
...entries,
require.resolve('./register'),
],
};
发布插件 #
javascript
// package.json
{
"name": "storybook-addon-my-addon",
"version": "1.0.0",
"main": "dist/preset.js",
"files": [
"dist/**/*",
"README.md"
],
"peerDependencies": {
"@storybook/api": "^7.0.0",
"@storybook/components": "^7.0.0",
"@storybook/manager-api": "^7.0.0",
"react": "^18.0.0"
},
"keywords": [
"storybook",
"addon"
]
}
插件 API #
addons.register #
注册插件:
javascript
import { addons } from '@storybook/manager-api';
addons.register('my-addon', (api) => {
// 插件逻辑
});
addons.add #
添加 UI 元素:
javascript
import { types } from '@storybook/manager-api';
addons.add('my-addon/panel', {
type: types.PANEL,
title: 'My Panel',
render: MyPanel,
});
addons.add('my-addon/tool', {
type: types.TOOL,
title: 'My Tool',
render: MyTool,
});
useParameter #
获取 Story 参数:
javascript
import { useParameter } from '@storybook/api';
const MyPanel = () => {
const myParam = useParameter('myParam', {});
return <div>{JSON.stringify(myParam)}</div>;
};
useStorybookState #
获取全局状态:
javascript
import { useStorybookState } from '@storybook/api';
const MyPanel = () => {
const { storyId, viewMode } = useStorybookState();
return <div>Current story: {storyId}</div>;
};
useChannel #
组件间通信:
javascript
import { useChannel } from '@storybook/api';
const MyPanel = () => {
const emit = useChannel({
'my-event': (data) => {
console.log('Received:', data);
},
});
return (
<button onClick={() => emit('my-event', { hello: 'world' })}>
Send Event
</button>
);
};
常用插件组合 #
完整开发环境 #
javascript
// .storybook/main.js
export default {
addons: [
// 核心功能
'@storybook/addon-essentials',
// 交互测试
'@storybook/addon-interactions',
// 无障碍
'@storybook/addon-a11y',
// 代码覆盖率
'@storybook/addon-coverage',
// 暗色模式
'storybook-dark-mode',
// 设计稿集成
'@storybook/addon-designs',
// 伪类状态
'storybook-addon-pseudo-states',
],
};
设计系统文档 #
javascript
// .storybook/main.js
export default {
addons: [
'@storybook/addon-essentials',
'@storybook/addon-designs',
'storybook-dark-mode',
'@storybook/addon-storysource',
],
};
最佳实践 #
1. 按需安装 #
javascript
// ✅ 只安装需要的插件
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
],
// ❌ 安装过多插件影响性能
addons: [
'@storybook/addon-essentials',
'@storybook/addon-a11y',
'@storybook/addon-coverage',
'storybook-dark-mode',
'@storybook/addon-designs',
// ... 更多插件
],
2. 合理配置 #
javascript
// 全局配置常用参数
export default {
parameters: {
backgrounds: {
default: 'light',
values: [
{ name: 'light', value: '#ffffff' },
{ name: 'dark', value: '#1a1a1a' },
],
},
},
};
3. 条件启用 #
javascript
// 只在特定 Story 启用插件
export const WithA11y = {
parameters: {
a11y: {
disable: false,
},
},
};
下一步 #
现在你已经掌握了插件系统的使用方法,接下来学习 测试 了解如何在 Storybook 中进行组件测试!
最后更新:2026-03-29