Medusa 设计系统 UI 组件 TSDoc 写作规范:从内联 Props 到 @excludeExternal 与 @keep 实战指南
发布时间:2026/9/11 12:14:38 锦皓数字建站

Medusa 设计系统 UI 组件 TSDoc 写作规范从内联 Props 到 excludeExternal 与 keep 实战指南【免费下载链接】medusaThe worlds most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusaMedusa 是一个面向开发者与 Agent 的开源电商平台其设计系统位于 packages/design-system/ui 包中UI 组件源码统一存放在packages/design-system/ui/src/components/目录下。本文围绕 Medusa 仓库内.claude/skills/writing-tsdocs/reference/ui-components.md这份参考规范系统讲解如何为这些 React UI 组件编写 TypeDocTSDoc注释包括组件级描述、内联 Props 文档、导出 Props 接口的写法以及excludeExternal、keep两个 Medusa 自定义标签的使用场景与判定标准。阅读完本文你将能够按照官方规范为 Medusa 设计系统组件乃至任何 Radix UI 包装组件写出与源码实际实现一致的、可被 TypeDoc 正确解析的高质量文档注释。一、规范背景谁在用这份文档在动手写注释之前需要先理解这份规范在整个文档体系中的位置。Medusa 仓库的.claude/skills/writing-tsdocs/SKILL.md定义了一个名为writing-tsdocs的写作技能Skill用于为 Medusa 代码库中的 TypeScript 源码添加或更新 TSDoc 注释覆盖 HTTP 类型、API 路由、UI 组件、数据模型、服务接口、JS SDK 方法、抽象 Provider、Workflow SDK、core-flows 工作流与步骤、事件等十余类目标。其中针对不同文件类型SKILL.md 规定必须先加载对应的参考文件路径模式需加载的参考文件packages/core/types/src/http/reference/http-types.mdpackages/medusa/src/api/admin/或/store/reference/api-routes.mdpackages/design-system/ui/src/components/reference/ui-components.mdpackages/modules/*/src/models/reference/data-models.mdpackages/core/types/src/非 HTTPreference/service-interfaces.mdpackages/core/js-sdk/src/等reference/service-interfaces.md也就是说只要编辑packages/design-system/ui/src/components/下的文件就必须以本文介绍的 ui-components.md 为准绳。同时 SKILL.md 还给出了一条硬性约束Medusa 自定义标签只能使用tsdoc.json 中定义过的标签包括expandable、featureFlag、since、apiIgnore、schema、tags、version、keep、customNamespace、namespaceMember等。本文要重点讲解的excludeExternal与keep正是其中两个二者在tsdoc.json中均以modifier修饰符语法定义。二、UI 组件的文档写作规则总览Medusa 设计系统的 UI 组件全部位于packages/design-system/ui/src/components/目录下每个组件一个子目录目录内通常包含xxx.tsx实现、xxx.stories.tsxStorybook 示例、xxx.spec.tsx测试三类文件。组件的导出方式既可以是具名导出也可以是默认导出。为这些组件编写 TSDoc 时必须遵守以下规则组件本身用一句话描述每个导出的组件都需要一段一句话的功能描述Props 采用内联注释在解构参数列表内部直接为每个 prop 写注释而不是在独立的interface上逐个加 TSDocRadix UI 组件引用原始基元primitive基于 Radix UI 的组件组件文档中要指明它基于哪个 Radix primitive完全透传的 Radix 组件只写组件文档如果组件把 props 原样转发给 Radix、没有任何自定义则只需写组件级描述props 由 Radix 提供不需要重复说明禁止使用param、returns、since组件不属于方法不要加这些标签since仅在提示词中明确给出版本号且组件为新增时才允许使用。需要特别强调的一点是永远不要为未导出的条目写 TSDocSKILL.md 的约束原文是 Never document unexported items并且不能修改逻辑、只能添加或修改注释块。三、组件级描述格式3.1 标准组件对于基于原生 HTML 元素的自定义组件描述应点明它基于哪个元素并概括其能力/** * A clickable button element with multiple visual styles and sizes. */ const Button React.forwardRefHTMLButtonElement, ButtonProps(...)以仓库中真实的 button.tsx 为例它的实际写法是/** * This component is based on the button element and supports all of its props */ const Button React.forwardRefHTMLButtonElement, ButtonProps(...)3.2 基于 Radix UI 的组件对于基于 Radix UI 的组件必须在组件文档中引用对应的 Radix primitive/** * This component is based on the [Radix UI Tabs](https://radix-ui.com/primitives/docs/components/tabs) primitive. */ const TabsRoot (props: React.ComponentPropsWithoutReftypeof RadixTabs.Root) {仓库中的 tabs.tsx 实际实现与此完全一致——TabsRoot将 props 直接透传给RadixTabs.Root并在组件上方标注了基于 Radix UI Tabs 的描述随后用Object.assign(TabsRoot, { Trigger, List, Content })组装出带命名空间子组件的Tabs导出。这种先写透传根组件、再组合子组件的模式在 Medusa 设计系统中非常常见。四、内联 Props 文档推荐模式UI 组件文档的核心特点是props 的注释写在解构参数列表内部每个 prop 上方紧贴一行注释。这样当开发者在 IDE 中悬停或 TypeDoc 解析时注释与具体 prop 的绑定关系一目了然// ✅ correct — inline prop docs const Button React.forwardRefHTMLButtonElement, ButtonProps( ( { /** * The buttons visual style. */ variant primary, /** * The buttons size. */ size base, /** * Whether to render as the child element instead of a button. */ asChild false, /** * Whether to show a loading spinner. */ isLoading false, disabled, className, children, ...props }, ref ) {这种写法与仓库源码高度吻合。在真实的 button.tsx 中variant、size、asChild、isLoading四个 prop 均有内联注释而className、children、disabled等来自原生button元素的通用属性则不加注释因为它们是继承属性见后文excludeExternal一节。对应的反面写法是在独立的interface上为每个属性逐个写/** ... */注释。规范明确给出反例// ❌ wrong — separate interface TSDoc for each prop interface ButtonProps { /** The buttons visual style. */ variant?: string // ... (document interface props separately only if the interface itself is exported) }唯一的例外如果Props接口本身是导出的供外部使用者引用类型则接口及其属性也需要完整文档化如果接口是文件内部私有的则优先使用解构参数上的内联注释。五、导出的 Props 接口文档当Props接口被导出时例如供开发者 import 后扩展或组合类型需要为接口本身和自定义属性都写上文档。典型写法/** * The props for the Button component. */ export interface ButtonProps extends React.ComponentPropsWithoutRefbutton, VariantPropstypeof buttonVariants { /** * Whether to show a loading spinner and disable interaction. */ isLoading?: boolean /** * Whether to render as the child element instead of a button. */ asChild?: boolean }注意观察两个细节接口通过extends React.ComponentPropsWithoutRefbutton继承原生button的全部 props包括className、disabled、onClick等通过VariantPropstypeof buttonVariants继承 cvaclass-variance-authority变体类型接口文档只需要覆盖自定义属性isLoading、asChild继承来的属性无需重复书写——这正是后面excludeExternal存在的意义。在 button.tsx 中ButtonProps接口的定义方式与上述示例完全一致且接口未导出文件内私有因此其属性的完整文档落在了解构参数的内联注释上。六、完整的前后对比示例规范的结尾给出一组修改前 / 修改后对照直观展示一份组件文件如何从无文档变为符合规范修改前无任何注释interface ButtonProps extends React.ComponentPropsWithoutRefbutton, VariantPropstypeof buttonVariants { isLoading?: boolean asChild?: boolean } const Button React.forwardRefHTMLButtonElement, ButtonProps( ({ variant primary, size base, asChild false, isLoading false, ...props }, ref) { // ... } )修改后组件描述 内联 props 注释interface ButtonProps extends React.ComponentPropsWithoutRefbutton, VariantPropstypeof buttonVariants { isLoading?: boolean asChild?: boolean } /** * This component is based on the button element and supports all of its props. */ const Button React.forwardRefHTMLButtonElement, ButtonProps( ( { /** * The buttons visual style. */ variant primary, /** * The buttons size. */ size base, /** * Whether to render as the child element instead of a button. */ asChild false, /** * Whether to show a loading spinner. */ isLoading false, ...props }, ref ) { // ... } )从对照中可以提炼出三条可复用的操作步骤在组件定义的正上方加一句基于元素/Radix primitive 的概述进入解构参数列表为每个自定义且非继承的 prop 加上内联注释注释只描述行为是否显示加载动画不重复类型信息类型已在接口中声明。七、excludeExternal隐藏继承的外部 Props7.1 为什么要隐藏继承属性Medusa 的 UI 组件大量使用接口继承原生元素类型的写法例如interface AlertProps extends React.ComponentPropsWithoutRefdiv。这样做的好处是使用者可以像用原生div一样使用Alert但代价是Props接口会继承一大堆外部属性——className、children、style、id、onClick等等——这些属性在生成的文档中会造成大量噪音。解决方案是在组件的 TSDoc 块中添加excludeExternal修饰符指示文档生成器抑制这些继承来的外部 props// AlertProps extends React.ComponentPropsWithoutRefdiv, which includes // className, children, style, id, onClick, etc. interface AlertProps extends React.ComponentPropsWithoutRefdiv { variant?: error | success | warning | info dismissible?: boolean } /** * This component is based on the div element and supports all of its props * * excludeExternal */ export const Alert React.forwardRefHTMLDivElement, AlertProps( ( { /** * The variant of the alert */ variant info, /** * Whether the alert is dismissible */ dismissible false, className, children, ...props }: AlertProps, ref ) {仓库中 alert.tsx 的实现与规范示例逐行对应AlertProps extends React.ComponentPropsWithoutRefdiv自定义属性只有varianterror | success | warning | info默认info和dismissible默认false组件 TSDoc 中同时含有excludeExternal。从实现看Alert内部还根据variant映射不同的图标XCircleSolid、CheckCircleSolid、ExclamationCircleSolid、InformationCircleSolid并维护dismissed状态控制关闭行为——这些都属于内部逻辑不需要在文档中展开文档职责只停留在组件是什么、自定义 prop 是什么。7.2 何时可以省略如果组件的Props接口只包含自定义属性、没有extends任何外部类型则excludeExternal可以省略——因为没有继承属性需要隐藏。八、keep保留特定的外部 PropsexcludeExternal是一刀切——它隐藏所有继承属性。但某些继承属性对使用者来说是有意义的、行为非显而易见的比如disabled在组件中有自定义的视觉状态如 CurrencyInput 禁用时的底色、光标样式变化onInvalid被组件内部逻辑接线如触发内部校验状态更新。此时可以在该 prop 的内联注释中使用keep将其保留在文档中并可配合defaultValue标注默认值/** * This component is based on the input element and supports all of its props * * excludeExternal */ const CurrencyInput React.forwardRefHTMLInputElement, CurrencyInputProps( ( { /** * The inputs size. */ size base, /** * Whether the input is disabled. * * keep * defaultValue false */ disabled, /** * A function that is triggered when the input is invalid. * * keep */ onInvalid, ...props }: CurrencyInputProps, ref ) {仓库中 currency-input.tsx 的实现与规范完全吻合CurrencyInputProps通过OmitReact.ComponentPropsWithoutReftypeof Primitive, prefix | suffix | size继承第三方库react-currency-input-field的输入属性组件 TSDoc 中标注了excludeExternal而disabled与onInvalid两个继承属性则用keep保留。从源码可以看到为什么它们值得保留disabled会触发text-ui-fg-disabled !bg-ui-bg-disabled ... cursor-not-allowed一整套禁用态样式onInvalid则被包装进onInnerInvalid回调——先更新valid状态驱动错误边框样式!shadow-borders-error再透传给外部调用者。这些非显而易见的自定义行为正是keep的适用场景。keep的使用判定标准✅ 对行为有自定义加工、使用者需要知晓的继承属性如disabled触发自定义视觉状态、onInvalid接入内部校验❌ 普通透传属性className、style、id等一律不要使用keep保持隐藏即可。九、Radix 透传组件最简文档最后一类常见形态是薄包装组件——它只负责加样式所有 props 原样转发给 Radix没有任何自定义 prop。此时文档只需要一行组件描述无需excludeExternal没有自定义 prop 可隐藏也无需任何单个 prop 的注释/** * This component is based on the [Radix UI Dialog](https://radix-ui.com/primitives/docs/components/dialog) primitive. */ const DialogRoot ( props: React.ComponentPropsWithoutReftypeof RadixDialog.Root ) { return RadixDialog.Root {...props} / }仓库中 tabs.tsx 的TabsRoot正是这一模式的实例props 类型直接取React.ComponentPropsWithoutReftypeof RadixTabs.Root函数体只有一行透传RadixTabs.Root {...props} /组件上方仅有一句基于 Radix UI Tabs primitive 的描述。这类组件使用者在查看文档时props 说明全部来自 Radix 官方文档Medusa 侧无需重复。十、总结UI 组件 TSDoc 决策速查场景组件级描述自定义 prop 注释excludeExternalkeep标准自定义组件基于原生元素一句话描述说明基于哪个元素解构参数内联注释接口 extends 了原生元素类型时添加仅在个别继承属性行为非显而易见时使用Radix 组件有自定义 prop引用 Radix primitive解构参数内联注释视接口是否继承外部类型而定同上Radix 透传组件无自定义 prop引用 Radix primitive不需要不需要不需要导出的 Props 接口接口一句话描述接口属性逐个注释——编写时还需遵守几条全局红线不为未导出的条目写注释不为测试文件*.spec.tsx、*.test.ts、__tests__/写注释不得虚构since版本号不得修改任何业务逻辑Medusa 自定义标签只能使用 tsdoc.json 中定义过的集合。遵循这套规范Medusa 设计系统生成的组件 API 文档才能保持结构统一、噪音最小让开发者与 Agent 都能快速定位组件做什么、每个自定义 prop 怎么用。【免费下载链接】medusaThe worlds most flexible commerce platform for agents and developers项目地址: https://gitcode.com/GitHub_Trending/me/medusa创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。