插件系统 #

插件概述 #

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'],
};

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