Storybook Addon 按 Story 局部禁用指南:通过 parameters 与 paramKey 实现面板级 disable
关联文档:docs/_snippets/button-story-disable-addon.md(配套讲解见 docs/addons/addon-knowledge-base.mdx) 适用对象:正在基于 Storybook 官方 addon API 编写自定义面板型 Addon 的开发者,以及需要在某些组件/story 上关闭面板型插件(如 a11y、docs、themes)的组件库维护者。
在 Storybook 中,一个 Addon 一旦注册,通常会在其管理界面(manager)的底部面板(PANEL)或工具栏中全局显示。但实际开发中常常需要"按需出现":某个 Addon 只对特定组件有意义(例如无障碍检测插件通常只在真实业务组件上才需要),当组件库中包含大量占位组件、Mock 组件时,无意义的插件面板反而会干扰使用。本指南基于 Storybook 官方仓库中的文档片段 button-story-disable-addon.md 及配套的注册片段 storybook-addon-disable-addon.md,完整讲解在 Story 层面通过parameters里的{ paramKey: { disable: true } }精确禁用单个 Addon 面板的实现原理、多框架写法与官方源码级佐证,读完可直接在自己的 stories 与自定义 Addon 中落地。
一、原理速览:paramKey+disable的约定
禁用机制由两部分配合完成:
- Addon 注册端:开发者在
manager.js/manager.tsx中用addons.add(PANEL_ID, { ... })注册面板时,需要声明一个paramKey元素,用于把该面板与某一组 Storybook parameters 关联起来:
addons.register(ADDON_ID, () => { addons.add(PANEL_ID, { type: types.PANEL, title: 'My Addon', render: () => <div>Addon tab content</div>, paramKey: 'myAddon', // this element }); });- 故事编写端:在某个 story 的
parameters中把该paramKey对应的对象里的disable置为true,该面板就会对这条 story 隐藏:
import type { Meta } from '@storybook/angular'; import { Button } from './button.component'; const meta: Meta<Button> = { component: Button, parameters: { myAddon: { disable: true }, // Disables the addon }, }; export default meta;也就是说:paramKey是 Addon 与 story 之间的"钥匙",parameters[paramKey].disable是开关状态。Addon 注册时声明的 key 必须与 story 中写入的 parameter 键名完全一致,否则 disable 不生效。
需要说明的是,这种按 story 的禁用只影响story 处于激活状态时面板的展示;如果当前选中的不是 story 而是文档页面等其它视图,Storybook 不会进行该过滤(源码行为见下文第四节)。
二、跨框架 / 跨写作范式的完整配置示例
本节完整继承关联文档中的所有写法变体。无论你使用 CSF 3 还是试验性的 CSF Next,禁用语法完全一致,仅meta的构造方式不同。
2.1 CSF 3 —— Angular
import type { Meta } from '@storybook/angular'; import { Button } from './button.component'; const meta: Meta<Button> = { component: Button, parameters: { myAddon: { disable: true }, // Disables the addon }, }; export default meta;2.2 CSF Next(🧪 试验特性)—— Angular / Web Components / React / Vue
CSF Next 使用从preview入口构造的preview.meta(...),parameters 的书写位置与 CSF 3 相同:
import preview from '../.storybook/preview'; import { Button } from './button.component'; const meta = preview.meta({ component: Button, parameters: { myAddon: { disable: true }, // Disables the addon }, });import preview from '../.storybook/preview'; const meta = preview.meta({ component: 'demo-button', parameters: { myAddon: { disable: true }, // Disables the addon }, });import preview from '../.storybook/preview'; import { Button } from './Button'; const meta = preview.meta({ component: Button, parameters: { myAddon: { disable: true }, // Disables the addon }, });import preview from '../.storybook/preview'; import { Button } from './Button'; const meta = preview.meta({ component: Button, parameters: { myAddon: { disable: true }, // Disables the addon }, });import preview from '../.storybook/preview'; import Button from './Button.vue'; const meta = preview.meta({ component: Button, parameters: { myAddon: { disable: true }, // Disables the addon }, });import preview from '../.storybook/preview'; import Button from './Button.vue'; const meta = preview.meta({ component: Button, parameters: { myAddon: { disable: true }, // Disables the addon }, });说明:CSF Next 是仓库文档中标注为 🧪 的试验性写作范式,要求从本地
../.storybook/preview导入并调用preview.meta(),好处是能继承全局类型与约定;若你的项目仍使用传统 CSF 3(export default导出 meta 对象),请参考下方 2.3/2.4 的写法。
2.3 CSF 3 —— React(JS)与通用 Web Components
import { Button } from './Button'; export default { /* 👇 The title prop is optional. * 用于配置 story 加载的更多细节,可参考 main.js 的 stories 配置项 */ title: 'Button', component: Button, parameters: { myAddon: { disable: true }, // Disables the addon }, };export default { component: 'demo-button', parameters: { myAddon: { disable: true }, // Disables the addon }, };注意:Web Components 中
component传入的是自定义元素标签名字符串'demo-button'而非组件构造函数,parameters 的用法与普通框架完全一致。
2.4 CSF 3 —— React(TS,带satisfies Meta<typeof Button>类型收窄)
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import type { Meta } from '@storybook/your-framework'; import { Button } from './Button'; const meta = { title: 'Button', component: Button, parameters: { myAddon: { disable: true }, // Disables the addon }, } satisfies Meta<typeof Button>; export default meta;import type { Meta } from '@storybook/web-components-vite'; const meta: Meta = { component: 'demo-button', parameters: { myAddon: { disable: true }, // Disables the addon }, }; export default meta;2.5 写法要点小结
| 维度 | 要点 |
|---|---|
| 目标层级 | 在meta(即组件级parameters)上声明,会对该组件下所有 story生效;如需精确到某一条 story,把相同结构写到该 story 对象的parameters里即可 |
| 键名 | myAddon仅是示例,必须与 Addon 注册时addons.add(..., { paramKey })的paramKey值一致(如官方 a11y 插件的键为a11y) |
| 值结构 | { disable: true },任何 truthy 值在官方实现中都能触发隐藏判断 |
| title | 从 CSF 3 开始 title 是可选的,省略时可依据文件位置自动推导,不影响 parameters 行为 |
| 类型安全 | TS 项目推荐satisfies Meta<typeof Button>(或: Meta<...>),让component与 Props 校验联动 |
三、禁用范围进阶:从"组件全部 stories"到"单条 story"
上文 meta 级声明表示:只要当前渲染的是该组件的任意一条 story,面板即被隐藏。若组件里只有少量 story 需要关闭插件,可以把同样的 parameter 下沉到 story 级别(字段结构不变,只是书写位置从export default meta移到具名 story 导出内部):
import type { Meta, StoryObj } from '@storybook/react-vite'; import { Button } from './Button'; const meta = { component: Button, } satisfies Meta<typeof Button>; export default meta; type Story = StoryObj<typeof meta>; // 只有这条 story 会关闭名为 myAddon 的面板 export const DoNotRunAddon: Story = { parameters: { myAddon: { disable: true }, }, };反之,若希望"除了个别 story 之外全局关闭",则可在 .storybook/preview 的全局parameters中开启 disable,再在需要启用的 story 上覆盖回{ disable: false },实现"默认关闭、白名单开启"的反向控制。
四、源码级验证:Storybook 官方是如何消费这个参数的
关联文档所描述的语法并非"约定俗成"的软约束,而是被 Storybook Manager 面板容器真实读取的硬逻辑。在 code/core/src/manager/container/Panel.tsx 中,面板列表的构建逻辑如下(第 47-67 行):
- 通过
api.getElements(Addon_TypesEnum.PANEL)拿到全部已注册面板; - 仅当
type === 'story'(即当前激活的是 story 而非其它视图)时才进行过滤; - 逐面板读取其
paramKey,判断parameters[paramKey]存在且.disable为 truthy,若是则把该面板从结果中剔除; - 除此之外,还支持两种声明式禁用:
p.disabled === true,或p.disabled为函数时执行p.disabled(parameters)得到布尔值(可用于更复杂的"按参数动态决定是否显示"场景); - 最终过滤后的
panels传入底层 AddonPanel 组件渲染。
核心判定片段对应:
const { paramKey }: any = p; if (paramKey && parameters && parameters[paramKey] && parameters[paramKey].disable) { return; // 从面板列表剔除 } if (p.disabled === true || (typeof p.disabled === 'function' && p.disabled(parameters))) { return; // 另外两种面板级禁用方式 }由此可以得出几个关键结论:
- 只要
paramKey匹配,disable 判断由容器统一完成,Addon 的render函数无需自行感知禁用状态; disable取 truthy/falsy,源码中未强制要求字面量true,但文档与官方示例统一使用{ disable: true }以保证可读性;- 面板级注册 API:
addons.add(id, { type: types.PANEL, paramKey, ... })中paramKey是 Panel 类型的合法声明字段,见 code/core/src/types/modules/addons.ts 中关于 panel 配置的类型定义。
五、把语法应用到官方 Addon:以 a11y 为例
如果不想自己实现 Addon,只想快速在某个 story 上关掉官方插件,上述语法同样适用。仓库中 code/addons/a11y/src/constants.ts 定义了无障碍插件的键名:
export const PARAM_KEY = `a11y`;而 code/addons/a11y/src/manager.tsx 在注册面板时把它作为paramKey传入,因此你无需知道内部实现,只要在 story 里写parameters: { a11y: { disable: true } }即可关掉 a11y 面板。其它将paramKey与参数键同名的官方 Addon 还有 docs、themes 等(可在 code/addons/docs/src/manager.tsx、code/addons/themes/src/manager.tsx 中看到同样的注册模式),这意味着"通过同名 parameter 传入{ disable: true }即可逐 story 关闭"是一个跨 Addon 的通用能力。
无障碍检测的典型使用场景是:Button的视觉回归 story 不需要跑 axe 规则,但可访问性基准 story(如带键盘导航的复杂组件)需要保留面板,此时就可通过 story 级parameters精确控制。
六、常见踩坑与排查清单
| 现象 | 原因 / 排查方向 |
|---|---|
写了{ disable: true }面板仍然显示 | ① 确认paramKey与 parameters 键名完全一致(大小写敏感);② 确认当前激活对象是 story(type === 'story');③ 检查 Addon 是否注册为types.PANEL |
| 组件下所有 story 都关了 | 参数写在meta级parameters,作用域天然是整个组件的 stories |
| 只想关某一条 | 把参数下沉到具体具名 story 的parameters字段 |
| disable 写错层级不生效 | 确认结构是{ [paramKey]: { disable: true } },不能写成{ disable: true }平铺在顶层 |
| CSF Next 不生效 | 确认preview.meta({ ... })返回的对象被export default导出,且parameters在 meta 顶层 |
七、配套文档继续阅读
- 注册语法与
addons.add面板类型:addon 注册示例 与 禁用配套注册片段 - 该语法所属的 Addon 知识库章节("Disable the addon panel"):addon-knowledge-base.mdx
- 面板容器过滤逻辑实现:code/core/src/manager/container/Panel.tsx
- 以
a11y为paramKey的官方实现:code/addons/a11y/src/manager.tsx、code/addons/a11y/src/constants.ts
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考