文档
Storybook 文档

插件 API

Storybook 的 API 允许开发人员以编程方式与 Storybook 进行交互。借助 API,开发人员可以构建和部署自定义插件和其他工具,以增强 Storybook 的功能。

核心插件 API

我们的 API 通过两个不同的包公开,每个包都有不同的用途

  • @storybook/manager-api 用于与 Storybook 管理器 UI 交互或访问 Storybook API。
  • @storybook/preview-api 用于控制和配置插件的行为。
my-addon/src/manager.js|ts
import { addons } from '@storybook/preview-api';
 
import { useStorybookApi } from '@storybook/manager-api';

addons.add()

add 方法允许您注册与插件关联的 UI 组件类型(例如,面板、工具栏、选项卡)。对于最小可行的 Storybook 插件,您应该提供以下参数

  • type:要注册的 UI 组件类型。
  • title:在插件面板中显示的标题。
  • render:渲染插件 UI 组件的函数。
my-addon/src/manager.js|ts
import React from 'react';
 
import { addons, types } from '@storybook/manager-api';
 
import { AddonPanel } from '@storybook/components';
 
const ADDON_ID = 'myaddon';
const PANEL_ID = `${ADDON_ID}/panel`;
 
addons.register(ADDON_ID, (api) => {
  addons.add(PANEL_ID, {
    type: types.PANEL,
    title: 'My Addon',
    render: ({ active }) => (
      <AddonPanel active={active}>
        <div> Storybook addon panel </div>
      </AddonPanel>
    ),
  });
});

渲染函数使用 active 调用。当面板在 UI 中聚焦时,active 值将为 true。

addons.register()

作为所有插件的入口点。它允许您注册插件并访问 Storybook 的 API。例如

my-addon/src/manager.js|ts
import { addons } from '@storybook/preview-api';
 
// Register the addon with a unique name.
addons.register('my-organisation/my-addon', (api) => {});

现在您将获得 StorybookAPI 的实例。请参阅 api 文档,了解有关使用 Storybook API 的信息。

addons.getChannel()

获取通道的实例,以与管理器和预览进行通信。您可以在插件注册代码和插件的包装器组件(在故事内部使用时)中找到它。

它具有与 NodeJS EventEmitter 兼容的 API。因此,您可以使用它来发出事件和侦听事件。

my-addon/src/manager.js|ts
import React, { useCallback } from 'react';
 
import { FORCE_RE_RENDER } from '@storybook/core-events';
import { addons } from '@storybook/preview-api';
import { useGlobals } from '@storybook/manager-api';
import { IconButton } from '@storybook/components';
import { OutlineIcon } from '@storybook/icons';
 
const ExampleToolbar = () => {
  const [globals, updateGlobals] = useGlobals();
 
  const isActive = globals['my-param-key'] || false;
 
  // Function that will update the global value and trigger a UI refresh.
  const refreshAndUpdateGlobal = () => {
    updateGlobals({
      ['my-param-key']: !isActive,
    }),
      // Invokes Storybook's addon API method (with the FORCE_RE_RENDER) event to trigger a UI refresh
      addons.getChannel().emit(FORCE_RE_RENDER);
  };
 
  const toggleToolbarAddon = useCallback(() => refreshAndUpdateGlobal(), [isActive]);
 
  return (
    <IconButton
      key="Example"
      active={isActive}
      title="Show the toolbar addon"
      onClick={toggleToolbarAddon}
    >
      <OutlineIcon />
    </IconButton>
  );
};

makeDecorator

使用 makeDecorator API 以官方插件的风格创建装饰器。如下所示

my-addon/src/decorator.js|ts
import { makeDecorator } from '@storybook/preview-api';
 
export const withAddonDecorator = makeDecorator({
  name: 'withSomething',
  parameterName: 'CustomParameter',
  skipIfNoParametersOrOptions: true
  wrapper: (getStory, context, { parameters }) => {
    /*
    * Write your custom logic here based on the parameters passed in Storybook's stories.
    * Although not advised, you can also alter the story output based on the parameters.
    */
    return getStory(context);
  },
});

如果故事的参数包含 { exampleParameter: { disable: true } }(其中 exampleParameter 是您插件的 parameterName),则不会调用您的装饰器。

makeDecorator API 需要以下参数

  • name:用于标识自定义插件装饰器的唯一名称。
  • parameterName:设置一个唯一的参数供插件使用。
  • skipIfNoParametersOrOptions:(可选) 如果用户没有通过 装饰器参数 提供选项,则不运行装饰器。
  • wrapper:您的装饰器函数。接受 getStorycontext 以及 optionsparameters(如上面 skipIfNoParametersOrOptions 中定义)。

Storybook API

Storybook 的 API 允许您访问 Storybook UI 的不同功能。

api.selectStory()

selectStory API 方法允许您选择单个故事。它接受以下两个参数;故事种类名称和一个可选的故事名称。例如

Button.stories.ts|tsx
import type { Meta, StoryObj } from '@storybook/react';
 
import { Button } from './Button';
 
const meta: Meta<typeof Button> = {
  /* 👇 The title prop is optional.
   * See https://storybook.org.cn/docs/configure/#configure-story-loading
   * to learn how to generate automatic titles
   */
  title: 'Button',
  component: Button,
  //👇 Creates specific parameters for the story
  parameters: {
    myAddon: {
      data: 'This data is passed to the addon',
    },
  },
};
 
export default meta;
type Story = StoryObj<typeof Button>;
 
/*
 *👇 Render functions are a framework specific feature to allow you control on how the component renders.
 * See https://storybook.org.cn/docs/api/csf
 * to learn how to use render functions.
 */
export const Basic: Story = {
  render: () => <Button>Hello</Button>,
};

以下是如何选择上述故事的

my-addon/src/manager.js|ts
addons.register('my-organisation/my-addon', (api) => {
  api.selectStory('Button', 'Default');
});

api.selectInCurrentKind()

类似于 selectStory API 方法,但它只接受故事作为唯一参数。

my-addon/src/manager.js|ts
addons.register('my-organisation/my-addon', (api) => {
  api.selectInCurrentKind('Default');
});

api.setQueryParams()

此方法允许您设置查询字符串参数。您可以将其用作插件的临时存储。以下是您定义查询参数的方式

my-addon/src/manager.js|ts
addons.register('my-organisation/my-addon', (api) => {
  api.setQueryParams({
    exampleParameter: 'Sets the example parameter value',
    anotherParameter: 'Sets the another parameter value',
  });
});

此外,如果您需要删除查询参数,请将其设置为 null,而不是从插件中删除它们。例如

my-addon/src/manager.js|ts
addons.register('my-organisation/my-addon', (api) => {
  api.setQueryParams({
    exampleParameter: null,
  });
});

api.getQueryParam()

允许检索通过 setQueryParams API 方法启用的查询参数。例如

my-addon/src/manager.js|ts
addons.register('my-organisation/my-addon', (api) => {
  api.getQueryParam('exampleParameter');
});

api.getUrlState(overrideParams)

此方法允许您获取应用程序 URL 状态,包括任何覆盖或自定义的参数值。例如

my-addon/src/manager.js|ts
addons.register('my-organisation/my-addon', (api) => {
  const href = api.getUrlState({
    selectedKind: 'kind',
    selectedStory: 'story',
  }).url;
});

api.on(eventName, fn)

此方法允许您注册一个处理程序函数,每当用户在故事之间导航时都会调用该函数。

my-addon/src/manager.js|ts
addons.register('my-organisation/my-addon', (api) => {
  // Logs the event data to the browser console whenever the event is emitted.
  api.on('custom-addon-event', (eventData) => console.log(eventData));
});

addons.setConfig(config)

此方法允许您覆盖默认的 Storybook UI 配置(例如,设置 主题 或隐藏 UI 元素)

.storybook/manager.js
import { addons } from '@storybook/manager-api';
 
addons.setConfig({
  navSize: 300,
  bottomPanelHeight: 300,
  rightPanelWidth: 300,
  panelPosition: 'bottom',
  enableShortcuts: true,
  showToolbar: true,
  theme: undefined,
  selectedPanel: undefined,
  initialActive: 'sidebar',
  sidebar: {
    showRoots: false,
    collapsedRoots: ['other'],
  },
  toolbar: {
    title: { hidden: false },
    zoom: { hidden: false },
    eject: { hidden: false },
    copy: { hidden: false },
    fullscreen: { hidden: false },
  },
});

下表详细说明了如何使用 API 值

名称类型描述示例值
navSize数字(像素)显示故事列表的侧边栏的大小300
bottomPanelHeight数字(像素)插件面板在底部位置时的大小200
rightPanelWidth数字(像素)插件面板在右侧位置时的大小200
panelPosition字符串显示插件面板的位置'bottom''right'
enableShortcuts布尔值启用/禁用快捷键true
showToolbar布尔值显示/隐藏工具栏true
theme对象Storybook 主题,请参阅下一节undefined
selectedPanel字符串选择插件面板的 IDstorybook/actions/panel
initialActive字符串选择移动设备上的默认活动选项卡sidebarcanvasaddons
sidebar对象侧边栏选项,请参阅下文{ showRoots: false }
toolbar对象使用插件 ID 修改工具栏中的工具{ fullscreen: { hidden: false } }

以下选项可在 sidebar 命名空间下进行配置

名称类型描述示例值
showRoots布尔值在侧边栏中将顶级节点显示为“根”false
collapsedRoots数组要默认视觉折叠的一组根节点 ID['misc', 'other']
renderLabel函数为树节点创建自定义标签;必须返回一个 ReactNode(item, api) => {item.name}

以下选项可在 toolbar 命名空间下进行配置

名称类型描述示例值
id字符串切换工具栏项目的可见性{ hidden: false }

Storybook 钩子

为了帮助简化插件开发并减少样板代码,API 公开了一组钩子来访问 Storybook 的内部结构。这些钩子是 @storybook/manager-api 包的扩展。

useStorybookState

它允许访问 Storybook 的内部状态。类似于 useglobals 钩子,我们建议优化您的插件以依赖 React.memo 或以下钩子;useMemouseCallback 以防止大量重新渲染周期。

my-addon/src/manager.js|ts
import React from 'react';
 
import { AddonPanel } from '@storybook/components';
 
import { useStorybookState } from '@storybook/manager-api';
 
export const Panel = () => {
  const state = useStorybookState();
  return (
    <AddonPanel {...props}>
      {state.viewMode !== 'docs' ? (
        <h2>Do something with the documentation</h2>
      ) : (
        <h2>Show the panel when viewing the story</h2>
      )}
    </AddonPanel>
  );
};

useStorybookApi

useStorybookApi 钩子是一个方便的助手,允许您完全访问 Storybook API 方法。

my-addon/manager.js|ts
import React, { useEffect, useCallback } from 'react';
 
import { useStorybookApi } from '@storybook/manager-api';
import { IconButton } from '@storybook/components';
import { ChevronDownIcon } from '@storybook/icons';
 
export const Panel = () => {
  const api = useStorybookApi();
 
  const toggleMyTool = useCallback(() => {
    // Custom logic to toggle the addon here
  }, []);
 
  useEffect(() => {
    api.setAddonShortcut('custom-toolbar-addon', {
      label: 'Enable toolbar addon',
      defaultShortcut: ['G'],
      actionName: 'Toggle',
      showInMenu: false,
      action: toggleAddon,
    });
  }, [api]);
 
  return (
    <IconButton key="custom-toolbar" active="true" title="Show a toolbar addon">
      <ChevronDownIcon />
    </IconButton>
  );
};

useChannel

允许设置对事件的订阅并获取发射器以向通道发出自定义事件。

可以在 iframe 和管理器上同时监听这些消息。

my-addon/manager.js|ts
import React from 'react';
 
import { AddonPanel, Button } from '@storybook/components';
 
import { STORY_CHANGED } from '@storybook/core-events';
 
import { useChannel } from '@storybook/manager-api';
 
export const Panel = () => {
  // Creates a Storybook API channel and subscribes to the STORY_CHANGED event
  const emit = useChannel({
    STORY_CHANGED: (...args) => console.log(...args),
  });
 
  return (
    <AddonPanel key="custom-panel" active="true">
      <Button onClick={() => emit('my-event-type', { sampleData: 'example' })}>
        Emit a Storybook API event with custom data
      </Button>
    </AddonPanel>
  );
};

useAddonState

useAddonState 是一个对需要数据持久性的插件很有用的钩子,这可能是由于 Storybook 的 UI 生命周期或涉及多种类型(例如,工具栏、面板)的更复杂插件造成的。

my-addon/manager.js|ts
import React from 'react';
 
import { useAddonState } from '@storybook/manager-api';
import { AddonPanel, IconButton } from '@storybook/components';
import { LightningIcon } from '@storybook/icons';
 
export const Panel = () => {
  const [state, setState] = useAddonState('addon-unique-identifier', 'initial state');
 
  return (
    <AddonPanel key="custom-panel" active="true">
      <Button onClick={() => setState('Example')}>
        Click to update Storybook's internal state
      </Button>
    </AddonPanel>
  );
};
export const Tool = () => {
  const [state, setState] = useAddonState('addon-unique-identifier', 'initial state');
 
  return (
    <IconButton
      key="custom-toolbar"
      active="true"
      title="Enable my addon"
      onClick={() => setState('Example')}
    >
      <LightningIcon />
    </IconButton>
  );
};

useParameteruseParameter 用于获取当前故事的参数。如果参数的值未定义,则会自动默认为定义的第二个值。

my-addon/manager.js|ts
import React from 'react';
 
import { AddonPanel } from '@storybook/components';
 
import { useParameter } from '@storybook/manager-api';
 
export const Panel = () => {
  // Connects to Storybook's API and retrieves the value of the custom parameter for the current story
  const value = useParameter('custom-parameter', 'initial value');
 
  return (
    <AddonPanel key="custom-panel" active="true">
      {value === 'initial value' ? (
        <h2>The story doesn't contain custom parameters. Defaulting to the initial value.</h2>
      ) : (
        <h2>You've set {value} as the parameter.</h2>
      )}
    </AddonPanel>
  );
};

useGlobals

对于依赖 Storybook 全局变量 的插件来说,这是一个非常有用的 Hook。它允许你获取和更新 global 值。我们还建议优化你的插件,使其依赖于 React.memo,或者以下 Hook:useMemouseCallback,以防止大量的重新渲染周期。

my-addon/manager.js|ts
import React from 'react';
 
import { AddonPanel, Button } from '@storybook/components';
 
import { useGlobals } from '@storybook/manager-api';
 
export const Panel = () => {
  const [globals, updateGlobals] = useGlobals();
 
  const isActive = globals['my-param-key'] || false; // 👈 Sets visibility based on the global value.
 
  return (
    <AddonPanel key="custom-panel" active={isActive}>
      <Button onClick={() => updateGlobals({ ['my-param-key']: !isActive })}>
        {isActive ? 'Hide the addon panel' : 'Show the panel'}
      </Button>
    </AddonPanel>
  );
};

useArgs

此 Hook 允许你检索或更新故事的 args

my-addon/src/manager.js|ts
import { useArgs } from '@storybook/manager-api';
 
const [args, updateArgs, resetArgs] = useArgs();
 
// To update one or more args:
updateArgs({ key: 'value' });
 
// To reset one (or more) args:
resetArgs((argNames: ['key']));
 
// To reset all args
resetArgs();

了解更多关于 Storybook 插件生态系统的信息