Addon API
Storybook 的 API 允许开发者以编程方式与 Storybook 交互。通过此 API,开发者可以构建和部署自定义插件及其他增强 Storybook 功能的工具。
核心 Addon API
我们的 API 通过两个不同的包公开,每个包都有不同的用途
storybook/manager-api
用于与 Storybook 管理器 UI 交互或访问 Storybook API。storybook/preview-api
用于控制和配置插件的行为。
import { addons } from 'storybook/preview-api';
import { useStorybookApi } from 'storybook/manager-api';
addons.add()
add
方法允许您注册与插件关联的 UI 组件类型(例如,面板、工具栏、选项卡)。对于一个最小可用的 Storybook 插件,您应该提供以下参数
type
:要注册的 UI 组件类型。title
:要在插件面板中显示的标题。render
:渲染插件 UI 组件的函数。
import React from 'react';
import { addons, types } from 'storybook/manager-api';
import { AddonPanel } from 'storybook/internal/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>
),
});
});
render 函数将使用 active
参数调用。当面板在 UI 中获得焦点时,active
的值为 true。
addons.register()
作为所有插件的入口点。它允许您注册插件并访问 Storybook API。例如
import { addons } from 'storybook/preview-api';
// Register the addon with a unique name.
addons.register('my-organisation/my-addon', (api) => {});
现在您将获得 StorybookAPI 的实例。有关 Storybook API 的使用,请参阅API 文档。
addons.getChannel()
获取通道实例以与管理器和预览进行通信。您可以在插件注册代码和插件的包装组件中找到此实例(在故事中使用时)。
它具有与 NodeJS EventEmitter 兼容的 API。因此,您可以使用它来触发事件和监听事件。
import React, { useCallback } from 'react';
import { OutlineIcon } from '@storybook/icons';
import { useGlobals } from 'storybook/manager-api';
import { addons } from 'storybook/preview-api';
import { IconButton } from 'storybook/internal/components';
import { FORCE_RE_RENDER } from 'storybook/internal/core-events';
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 创建官方插件风格的 decorators。如下所示
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
),您的 decorator 将不会被调用。
makeDecorator
API 需要以下参数
name
:用于标识自定义插件 decorator 的唯一名称。parameterName
:设置供插件使用的唯一参数。skipIfNoParametersOrOptions
:(可选)如果用户未通过decorators或parameters设置选项,则不运行此 decorator。wrapper
:您的 decorator 函数。接收getStory
、context
以及options
和parameters
(如上文skipIfNoParametersOrOptions
中所定义)。
Storybook API
Storybook 的 API 允许您访问 Storybook UI 的各种功能。
api.selectStory()
selectStory
API 方法允许您选择单个故事。它接受以下两个参数:故事种类名称和可选的故事名称。例如
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { Button } from './Button';
const meta = {
/* 👇 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',
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
/*
*👇 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>,
};
您可以按如下方式选择上述故事
addons.register('my-organisation/my-addon', (api) => {
api.selectStory('Button', 'Default');
});
api.selectInCurrentKind()
与 selectStory
API 方法类似,但它只接受故事作为唯一参数。
addons.register('my-organisation/my-addon', (api) => {
api.selectInCurrentKind('Default');
});
api.setQueryParams()
此方法允许您设置查询字符串参数。您可以将其用作插件的临时存储。以下是定义查询参数的方法
addons.register('my-organisation/my-addon', (api) => {
api.setQueryParams({
exampleParameter: 'Sets the example parameter value',
anotherParameter: 'Sets the another parameter value',
});
});
此外,如果您需要移除查询参数,请将其设置为 null
,而不是从插件中移除它们。例如
addons.register('my-organisation/my-addon', (api) => {
api.setQueryParams({
exampleParameter: null,
});
});
api.getQueryParam()
允许检索通过 setQueryParams
API 方法启用的查询参数。例如
addons.register('my-organisation/my-addon', (api) => {
api.getQueryParam('exampleParameter');
});
api.getUrlState(overrideParams)
此方法允许您获取应用程序 URL 状态,包括任何覆盖或自定义的参数值。例如
addons.register('my-organisation/my-addon', (api) => {
const href = api.getUrlState({
selectedKind: 'kind',
selectedStory: 'story',
}).url;
});
api.on(eventName, fn)
此方法允许您注册一个处理函数,该函数在用户在故事之间导航时被调用。
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 元素)
import { addons, type State } from 'storybook/manager-api';
addons.setConfig({
navSize: 300,
bottomPanelHeight: 300,
rightPanelWidth: 300,
panelPosition: 'bottom',
enableShortcuts: true,
showToolbar: true,
theme: undefined,
selectedPanel: undefined,
initialActive: 'sidebar',
layoutCustomisations: {
showSidebar(state: State, defaultValue: boolean) {
return state.storyId === 'landing' ? false : defaultValue;
},
showToolbar(state: State, defaultValue: boolean) {
return state.viewMode === 'docs' ? false : defaultValue;
},
},
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 | 字符串 | 选择插件面板的 ID | storybook/actions/panel |
initialActive | 字符串 | 选择移动设备上的默认活动选项卡 | sidebar 或 canvas 或 addons |
sidebar | 对象 | 侧边栏选项,请参见下文 | { showRoots: false } |
toolbar | 对象 | 使用插件 ID 修改工具栏中的工具 | { fullscreen: { hidden: false } } |
以下选项可在 sidebar
命名空间下配置
名称 | 类型 | 描述 | 示例值 |
---|---|---|---|
showRoots | 布尔值 | 在侧边栏中将顶级节点显示为“根节点” | false |
collapsedRoots | 数组 | 默认在视觉上折叠的根节点 ID 集 | ['misc', 'other'] |
renderLabel | 函数 | 为树节点创建自定义标签;必须返回一个 ReactNode | (item, api) => {item.name} |
以下选项可在 toolbar
命名空间下配置
名称 | 类型 | 描述 | 示例值 |
---|---|---|---|
[id] | 字符串 | 切换特定工具栏项(例如 title , zoom )的可见性 | { hidden: false } |
Storybook hooks
为了简化插件开发并减少样板代码,API 公开了一组 hooks 以访问 Storybook 的内部机制。这些 hooks 是 storybook/manager-api
模块的扩展。
useStorybookState
它允许访问 Storybook 的内部状态。与 useglobals
hook 类似,我们建议优化您的插件,使其依赖于 React.memo
或以下 hooks:useMemo
、useCallback
,以防止大量的重新渲染循环。
import React from 'react';
import { AddonPanel } from 'storybook/internal/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
hook 是一个方便的辅助工具,允许您完全访问Storybook API 方法。
import React, { useEffect, useCallback } from 'react';
import { useStorybookApi } from 'storybook/manager-api';
import { IconButton } from 'storybook/internal/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
允许设置事件订阅并获取 emitter 以向通道触发自定义事件。
可以在 iframe 和管理器上监听消息。
import React from 'react';
import { useChannel } from 'storybook/manager-api';
import { AddonPanel, Button } from 'storybook/internal/components';
import { STORY_CHANGED } from 'storybook/internal/core-events';
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
是一个有用的 hook,适用于需要数据持久化的插件,无论是由于 Storybook 的 UI 生命周期,还是涉及多种类型(例如,工具栏、面板)的更复杂插件。
import React from 'react';
import { useAddonState } from 'storybook/manager-api';
import { AddonPanel, IconButton } from 'storybook/internal/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>
);
};
useParameter
useParameter
获取当前故事的参数。如果参数值未定义,它将自动默认为定义的第二个值。
import React from 'react';
import { AddonPanel } from 'storybook/internal/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
或以下 hooks:useMemo
、useCallback
,以防止大量的重新渲染循环。
import React from 'react';
import { AddonPanel, Button } from 'storybook/internal/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
允许您检索或更新故事的 args
的 hook。
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 插件生态系统的信息