Storybook Controls 面板:按属性禁用控件(table.disable 与 control:false 详解)
本文基于 Storybook 官方文档中 Controls 面板的"Disable controls for specific properties"特性展开,系统讲解如何在单个组件属性上关闭 Controls 控件:argTypes中table: { disable: true }的完整多框架用法(React、Vue、Svelte、Angular、Web Components,含 CSF 3 与 CSF Next 两种写法),它与control: false的关键区别,以及在 Story 级应用的更细粒度模式。文末结合仓库源码(Title.tsx、ArgsTable.tsx)说明该配置在 Manager 与 Docs 两处 UI 中的实际过滤逻辑,帮助你在编写 Story 时做出正确的"隐藏"决策。
一、背景:为什么需要按属性禁用 Controls
Controls 是 Storybook 中用于实时编辑 Story 属性(args)的官方 Addon,它与 Docs 共用同一套渲染引擎——因此 Controls 面板本质上是一个内嵌的Controlsdoc block(即 ArgsTable 的一种形态)。这意味着每个属性在面板中对应一行,行内同时包含:
- 可交互的控件(输入框、下拉、颜色选择器等);
- 属性的文档信息(描述、默认值、表格行)。
当你希望某个属性对使用者只读(例如内部透传的id、调试用的__testMode,或文档已单独说明的私有属性)时,就需要"单独关掉这个属性的控件"。Storybook 提供了两条正交的配置路径:
| 配置位置 | 写法 | 效果 |
|---|---|---|
argTypes.foo.table.disable | table: { disable: true } | 从 Controls 面板 UI 中完全移除该属性的控件行,同时从文档表格中移除该属性行 |
argTypes.foo.control | control: false | 保留文档行(描述/默认值仍展示),但该行没有可交互控件 |
两条路径都定义在 Story 文件的默认导出(meta)中,且都可以在单个 Story 上覆盖(与 decorators 等其它 Storybook 特性相同的"meta 定义、story 覆盖"层级模式)。
二、禁用单个属性控件:table.disable: true
下面的示例都针对一个想从 UI 中移除的属性foo,来自官方文档代码片段 component-story-disable-controls.md,并按框架整理。
React / 通用 TypeScript(CSF 3)
// 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 { YourComponent } from './YourComponent'; const meta = { component: YourComponent, argTypes: { // foo is the property we want to remove from the UI foo: { table: { disable: true, }, }, }, } satisfies Meta<typeof YourComponent>; export default meta;要点:
argTypes.foo只写table.disable,不写control、options等其它字段;satisfies Meta<typeof YourComponent>提供类型检查但不丢失字面量类型,是 TS 项目推荐的声明方式。
JavaScript 项目(无 TS)的等价写法:
import { YourComponent } from './YourComponent'; export default { component: YourComponent, argTypes: { // foo is the property we want to remove from the UI foo: { table: { disable: true, }, }, }, };Vue
import type { Meta } from '@storybook/your-framework'; import YourComponent from './YourComponent.vue'; const meta = { component: YourComponent, argTypes: { foo: { table: { disable: true }, }, }, } satisfies Meta<typeof YourComponent>; export default meta;Angular
import type { Meta } from '@storybook/angular'; import { YourComponent } from './your-component.component'; const meta: Meta<YourComponent> = { component: YourComponent, argTypes: { // foo is the property we want to remove from the UI foo: { table: { disable: true, }, }, }, }; export default meta;Svelte
Svelte 框架下既有传统的 CSF 3 文件式写法,也有 Svelte CSF 的<script module>写法:
// Replace your-framework with svelte-vite or sveltekit import type { Meta } from '@storybook/your-framework'; import YourComponent from './YourComponent.svelte'; const meta = { component: YourComponent, argTypes: { // foo is the property we want to remove from the UI foo: { table: { disable: true, }, }, }, } satisfies Meta<typeof YourComponent>; export default meta;<script module> import { defineMeta } from '@storybook/addon-svelte-csf'; import YourComponent from './YourComponent.svelte'; const { Story } = defineMeta({ component: YourComponent, argTypes: { // foo is the property we want to remove from the UI foo: { table: { disable: true, }, }, }, }); </script>Web Components
Web Components 没有 docgen,component直接写标签名字符串:
export default { component: 'your-component', argTypes: { // foo is the property we want to remove from the UI foo: { table: { disable: true, }, }, }, };import type { Meta } from '@storybook/web-components-vite'; const meta: Meta = { component: 'your-component', argTypes: { foo: { table: { disable: true, }, }, }, }; export default meta;CSF Next(实验语法)下的等价写法
在采用 CSF Next 实验特性(官方代码块标注为 "CSF Next 🧪")的项目中,meta 不再使用export default,而是通过preview.meta()工厂函数生成,配置结构完全一致:
import preview from '../.storybook/preview'; import { YourComponent } from './YourComponent'; const meta = preview.meta({ component: YourComponent, argTypes: { // foo is the property we want to remove from the UI foo: { table: { disable: true, }, }, }, });Vue / Svelte / Web Components 的 CSF Next 版本写法相同,只是component字段按各自框架填组件引用或标签名。该语法当前仍处于实验阶段,仓库文档中通过独立的 "CSF Next 🧪" 代码块 Tab 提供,生产项目请确认所用 Storybook 版本支持后再切换。
三、与 control: false 的关键区别:保留文档行
官方 controls 文档 在展示table.disable的 UI 变化后特别指出:上面的示例同时把该属性的文档从表格中移除了。多数场景这样没问题;但如果你希望"没有控件、仍保留属性文档(描述/默认值)",应改用control: false,对应片段为 component-story-disable-controls-alt.md:
import type { Meta } from '@storybook/your-framework'; import { YourComponent } from './YourComponent'; const meta = { component: YourComponent, argTypes: { // foo is the property we want to remove from the UI foo: { control: false, }, }, } satisfies Meta<typeof YourComponent>; export default meta;对比总结:
| 场景 | 推荐写法 |
|---|---|
| 属性完全不该出现在 Controls 面板 / Args 表格中(内部属性、透传属性) | table: { disable: true } |
| 属性文档要保留(例如说明"该值由运行时注入"),但禁止手动修改 | control: false |
| 只针对某个 Story 生效,其它 Story 仍可编辑 | 在单个 Story 的argTypes中覆盖(见下一节) |
注意两者作用域:table.disable影响的是 Controls 面板与 Docs 中 ArgsTable 的"行级展示",它不是运行时过滤——组件渲染时该属性依然会按 args 正常传入,只是 UI 层不再展示对应的编辑行。
四、在 Story 级应用同一模式
与 decorators、parameters 等其它 Storybook 属性一致,argTypes支持"meta 定义、story 覆盖"的层级结构,因此可以在单个 Story 上做更细粒度的禁用:
import type { Meta, StoryObj } from '@storybook/your-framework'; import { YourComponent } from './YourComponent'; const meta: Meta<typeof YourComponent> = { component: YourComponent, }; export default meta; type Story = StoryObj<typeof meta>; // 只在 Debug 这个 Story 中隐藏 foo 的控件 export const Debug: Story = { argTypes: { foo: { table: { disable: true }, }, }, };这样Primary等其它 Story 仍能看到并编辑foo,而DebugStory 中该行被移除,适合"某个变体中该属性无意义"的情况。
五、源码级实现:table.disable 在哪里被消费
仓库源码印证了第二节的两种行为差异,核心过滤点有两处:
Manager 侧 Controls 面板标题的计数。Controls 面板头部会显示当前可用控件数量,其计数逻辑在 Title.tsx:
const rows = useArgTypes(); const controlsCount = Object.values(rows).filter( (argType) => argType?.control && !argType?.table?.disable ).length;即一个 argType 被计入"控件"必须同时满足:显式拥有
control(control为false时argType.control为 falsy,自然被排除)且table.disable不为真。这解释了为什么table.disable的属性从面板计数与 UI 中消失,而control: false的属性只是"没控件但行还在"。Docs 侧 ArgsTable 的行过滤。Docs 的 ArgsTable 在渲染行时执行 ArgsTable.tsx 中的过滤:
(row) => !row?.table?.disable && safeIncludeConditionalArg(row, args || {}, globals || {})这里
table.disable为真的行被直接剔除,与 Controls 面板的title.disable表现一致;而同一表达式中的safeIncludeConditionalArg则处理 Controls 的if条件控制(见 controls 文档"Conditional controls"小节),说明"属性行过滤"是 Controls 与 Docs 共用的引擎能力。
可以推断:由于两处过滤共享同一份 argTypes 解析结果,table.disable的效果在 Controls 面板与 Docs 页面的 Args 表格中是同步的——这正是官方文档用一段视频展示"UI 前后变化"的原因(文档中引用的演示视频为 addon-controls-disable-specific-prop-optimized.mp4)。
六、相关能力:include/exclude 过滤与排序
如果需求是"隐藏一大批属性"而非单个属性,controls 文档 在 "Filtering controls" 小节提供了更高层的controls参数方案:可选的include/exclude字段,接受字符串数组或正则表达式,可全局或按 Story 定义。两者可组合使用——include/exclude处理批量过滤,table.disable/control: false处理精确到单个属性的展示控制。
此外同一文档还说明 Controls 的排序策略:默认按 args 数据处理顺序(none),也支持按属性名字母序(alpha)或"必填项优先"(requiredFirst),可在 meta 的controls参数中配置。
七、小结
- 移除某属性在 Controls 面板中的控件行与文档行:
argTypes.foo.table.disable = true; - 只移除控件、保留属性文档:
argTypes.foo.control = false; - 需要在单个 Story 生效:把同样的
argTypes片段写在 Story 导出上; - 批量过滤用
controls参数的include/exclude(字符串数组或正则),单属性精确控制用上述两种 argTypes 写法; - 源码层面,
table.disable分别在 Title.tsx 的控件计数与 ArgsTable.tsx 的行过滤中被消费,验证了 Controls 与 Docs 共用行级过滤机制。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考