首页
/ Storybook Props Tables 深度指南:Docs Addon 组件属性表的生成、自定义与排障

Storybook Props Tables 深度指南:Docs Addon 组件属性表的生成、自定义与排障

2026-09-06 12:34:50作者:卓炯娓

Storybook Docs Addon 会为受支持的框架(React、Vue 3、Angular、Web Components、Ember)自动生成组件的属性表格(Props Tables),将组件的每个 prop 的名称、类型、默认值、描述与动态 Controls 汇聚到一张可交互的表中。本篇基于 Storybook 仓库中的 props-tables.md 文档展开,结合 @storybook/addon-docs 的真实源码实现,完整覆盖属性表的使用方式(DocsPage 与 MDX)、Controls 集成机制、通过 ArgTypes 进行深度自定义的合并规则、出 bug 时的最小复现排障流程,以及各框架底层 docgen 子包的已知局限性,帮助你在自己的项目中生成准确、可定制的组件文档表格。

Storybook Docs 生成的 Props Table 示例,展示组件属性的名称、类型、默认值与 Controls 列

Props Table 的工作机制:从组件到 ArgTypes 数据结构

在讲用法之前,先理解属性表的数据来源,这决定了后面所有自定义行为。

Props table 是从一个内部数据结构 ArgTypes 渲染出来的。当你在 story 的 component 元数据中声明了组件后,Docs Addon 会根据组件的属性自动提取 ArgTypes。从源码结构看,这一提取动作委托给了渲染器(renderer)注册的 docgen 提取器:ArgTypes 块通过 parameters.docs.extractArgTypes 调用框架对应的提取逻辑,若当前框架不支持则抛出 Args unsupported 错误(见 argTypesShared.ts 中的 extractComponentArgTypes 函数):

// code/addons/docs/src/blocks/blocks/argTypesShared.ts(节选)
export function extractComponentArgTypes(
  component: Renderer['component'],
  parameters: Parameters
): StrictArgTypes {
  const { extractArgTypes }: { extractArgTypes: ArgTypesExtractor } = parameters.docs || {};
  if (!extractArgTypes) {
    throw new Error(ArgsTableError.ARGS_UNSUPPORTED);
  }
  return extractArgTypes(component) as StrictArgTypes;
}

以 React 为例,提取链路由框架 preset 装配:framework-preset-react-docs(见 framework-preset-react-docs.ts)会把基于 react-docgen / react-docgen-typescript 的提取器注册进 docs 参数,核心包的 enhanceArgTypes.ts 再负责对提取结果做增强(补全 control 推断、默认值格式化等)。

因此,ArgTypes 中每个字段分两类:

  • 标准字段nametypedefaultValuedescription——所有框架通用(类比 React 的 PropTypes);
  • Addon 注解字段tablecontrol——用于分别定制表格渲染与控制器行为。

理解了这层结构,后面的“通过覆写 argTypes 元数据定制表格”就是顺理成章的。

使用方式

框架级别的安装与初始化请参考各渲染器的 README:ReactVue 3AngularWeb ComponentsEmber

DocsPage:通过 component 元数据获得表格

DocsPage(即每个 story 文件自动生成的文档页)中,只需在 stories 元数据中导出 component 属性,属性表就会自动生成:

// MyComponent.stories.js
import { MyComponent } from './MyComponent';

export default {
  title: 'MyComponent',
  component: MyComponent,
};
// stories etc...

ArgTypes.tsx 的实现看,of 参数缺省时解析到当前页面的 meta(useOf(of || 'meta')),preparedMeta.argTypes 由 preview 侧的 prepareStory/prepareMeta 流程准备好,包含 docgen 提取结果与你手动声明的 argTypes 合并后的行数据。

MDX:使用 ArgsTable

MDX 文档中,则直接使用 ArgsTable 块嵌入属性表:

// MyComponent.stories.mdx
import { ArgsTable } from '@storybook/addon-docs';
import { MyComponent } from './MyComponent';

# My Component!

<ArgsTable of={MyComponent} />

注意 <ArgsTable of={MyComponent} /><ArgsTable story="xxx" /> 两种构造有本质区别:前者直接以组件为输入做 docgen 提取,后者消费 story 上下文里准备好的 argTypes。这直接影响后文“哪些自定义对哪种构造生效”的规则。

Controls:属性表中的内置动态控件

从 Storybook 6.0 起,ArgsTable 块内置了 Controls(早期称为 "knobs"),可以对 story 进行动态编辑。当你的 story 以 Storybook Args 作为输入时,这些控件会自动出现在属性表中。DocsPage 与 MDX 的触发方式略有不同。

ArgsTable 中的 Controls 动态编辑演示

DocsPage.DocsPage 中,只要把 story 写成消费 args 的形式,自动生成的属性表就会在最右侧一列展示 controls:

export default {
  title: 'MyComponent',
  component: MyComponent,
};

export const WithControls = (args) => <MyComponent {...args} />;

MDX.MDX 中,ArgsTable 的 controls 比 DocsPage 更灵活。要显示 controls,ArgsTable 必须绑定到一个 story 而不是一个组件:

<Story name="WithControls">
  {args => <MyComponent {...args} />}
</Story>

<ArgsTable story="Controls" />

对照源码可以印证这一差异:纯表格组件 ArgsTable.tsx 只有在收到 updateArgs 回调(由 story 上下文注入)时,才会在表头多渲染一列 Control,并在表格右上角显示 “Reset controls” 按钮(调用 resetArgs)。换句话说,没有绑定可交互 story 的 ArgsTable 天然没有 Control 列——这正是 MDX 中必须使用 story="xxx" 构造的原因。

关于如何编写使用 controls 的 story 的详细教程,可参考 Storybook 官方的 Controls 文档(Essentials 章节)。

自定义属性表

Props table 是从组件和 story 自动推断出来的,但很多时候你希望定制最终呈现。定制的手段就是覆写 ArgTypes 数据。这一能力目前对 DocsPage<ArgsTable story="xxx" /> 构造可用,而 <ArgsTable of={component} /> 构造不适用(因为后者绕过 story 的 argTypes,直接对组件做提取)。

通过 Customizing ArgTypes 覆写字段

注意: 该 API 是实验性的,可能会在常规 semver 发布周期之外发生变化。

当你在 DocsPage 中声明了 component,或在 MDX 中使用 <ArgsTable story="xxx" /> 构造时,属性表展示的是被 Storybook 提取出来的 story.argTypes

考虑以下输入:

// Button.js
import React from 'react';
import PropTypes from 'prop-types';

export const Button = ({ label }) => <button>{label}</button>;
Button.propTypes = {
  /** Demo description */
  label: PropTypes.string,
};
Button.defaultProps = {
  label: 'Hello',
};

// Button.stories.js
export default { title: 'Button', component: Button };

这会对 Button 组件生成如下等价的内存数据结构:

const argTypes = {
  label: {
    name: 'label',
    type: { name: 'string', required: false },
    defaultValue: 'Hello',
    description: 'demo description',
    table: {
      type: { summary: 'string' },
      defaultValue: { summary: 'Hello' },
    }
    control: {
      type: 'text'
    }
  }
}

在这份 ArgTypes 数据结构中,nametypedefaultValuedescription 是所有 ArgTypes 的标准字段(类比 React 的 PropTypes);tablecontrol 字段则是 addon 特有的注解——例如 table 注解提供定制 label 如何在表格中渲染的额外信息,control 注解提供该属性编辑控件的额外信息。

作为用户,你可以通过选择性地覆写这些值来定制属性表。对上面的 Button.stories.js 做如下修改:

export default {
  title: 'Button',
  component: Button,
  argTypes: {
    label: {
      description: 'overwritten description',
      table: {
        type: { summary: 'something short', detail: 'something really long' },
      },
      control: {
        type: null,
      },
    },
  },
};

这些值——descriptiontable.typecontrol.type——会与 Storybook 提取的默认值做深度合并。最终合并结果为:

const argTypes = {
  label: {
    name: 'label',
    type: { name: 'string', required: false },
    defaultValue: 'Hello',
    description: 'overwritten description',
    table: {
      type: { summary: 'something short', detail: 'something really really long' },
      defaultValue: { summary: 'Hello' },
    }
    control: {
      type: null
    }
  }
}

渲染效果是:一行带有被改写的描述、带下拉展开详情的类型展示、且不显示 control。

提示: @storybook/addon-docs 为常见场景提供了简写形式:

  • type: 'number' 等价于 type: { name: 'number' }
  • control: 'radio' 等价于 control: { type: 'radio' }

Controls 的定制还有完整的文档章节(Essentials 中的 Controls #configuration),此处不再展开。

可定制的表格字段一览

control 之外,属性表支持以下定制字段:

字段 说明
name 属性名
type.required 该属性是否必填
description 属性的 Markdown 描述
table.type.summary 类型的简短版本
table.type.detail 类型的详细版本(当类型较复杂时)
table.defaultValue.summary 默认值的简短版本
table.defaultValue.detail 默认值的详细版本(当值较复杂时)
control 参见 addon-controls 文档(Essentials #configuration)

源码视角:分组、排序与过滤

原文档的表格字段之外,当前仓库源码还展示了若干在表格渲染层的直接能力,供深入定制时参考:

  • 行级隐藏与条件显示ArgsTable.tsx 在渲染前会用 pickBy 过滤行数据,table.disable 为真的行被剔除;带 if 条件的行则通过 includeConditionalArg(来自 CSF 工具)依据当前 args/globals 决定显示与否。
  • 分组groupRows 函数按 table.categorytable.subcategory 两级把行组织成 Section/Subsection 渲染,table.category 对应的正是 table 注解中的分组能力。
  • 排序ArgsTable 支持 sort 参数,取值为 'alpha' | 'requiredFirst' | 'none'(默认 'none'),分别对应按名称字母序、必填项优先、保持原序。
  • 块级过滤ArgTypes 块接受 include / excludePropDescriptor)与 sort 属性,也可通过 parameters.docs.argTypes 统一配置;未显式传入时回退到参数配置(见 ArgTypes.tsxfilterProps 的解析逻辑),实际过滤由 preview-api 的 filterArgTypes 完成。

报告 Bug:最小复现排障流程

从源码中提取组件属性是一个拥有成千上万边界情况的棘手问题。Storybook 把这个包及其测试设计成能精准定位问题归属——因为 bug 可能出在本包,也可能(更常见地)出在它依赖的某个子包。

如果你发现属性表有问题,建议按以下步骤排查:

  1. 先查已知限制。看你的场景是否已有对应测试用例,如果有,它会记录在下文“已知限制”一节中,且本包内应存在一个或多个对应的测试 fixture。例如使用 React 时,可查阅各框架的 docgen 测试与 fixture 目录(在本仓库中,核心提取逻辑位于 argTypes 目录,跨框架 docgen 对比测试位于 docgen-harness)。
  2. 如果你的问题尚未被覆盖,请:
    1. 创建一个最小化的问题复现,每个 case 只有几行代码;
    2. 放到对应的 __testfixtures__ 目录中,例如 ./src/frameworks/<framework>/__testfixtures__/XXXX-some-descriptionXXXX 为对应的 GitHub issue 编号);
    3. 运行对应框架的测试,例如 yarn jest --testPathPattern=react-properties.test.ts --watch
    4. 检查你的测试用例的输出文件;
    5. 把示例加入对应的 stories 文件(React 即 react-properties.stories.ts)以获得可视化复现。

如果问题出在本库,请提 issue 并附一个包含复现用例的 PR。如果问题出在子包,请到相应子包提 issue,在下方“已知限制”中记录该限制、链接到该 issue,并提交包含文档更新与 fixture/快照的 PR。

已知限制

本包依赖多个子包来从组件中提取属性信息,很多 bug 实际对应子包的 bug。由于 Storybook 不维护这些子包,当前能做到的最佳实践是:(1) 记录这些限制;(2) 向子包提供干净的复现;(3) 可选地给这些包提 PR 修复问题。

框架 底层库 框架文档
React react-docgenreact-docgen-typescript React 文档
Vue 3 vue-docgen-api Vue 3 文档
Angular compodoc(本仓库另有 angular-compodoc 集成包) Angular 文档
Web Components custom-elements.json Web Components 文档
Ember yui-doc Ember 文档

各框架 props tables 的详细配置说明见对应渲染器 README 中的 "Props Tables" 章节(如 React 渲染器 README),以及多框架混用场景的 多框架指南

更多资源

登录后查看全文
热门项目推荐
相关项目推荐