Dify UI 选择型组件契约指南:RadioGroup、Combobox 与 Select 如何选、如何用、如何保持类型安全 Dify UI 选择型组件契约指南RadioGroup、Combobox 与 Select 如何选、如何用、如何保持类型安全【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify本文基于 Dify 前端组件库langgenius/dify-ui的官方契约文档 Selection 展开系统讲解该组件库中六类选择型原语RadioGroup、SegmentedControl、Tabs、Autocomplete、Combobox、Select的选择规则、值类型泛型契约、弹出层宽度约束以及Radio家族Radio/RadioItem/RadioControl的解剖结构。读完后你将能够在 Dify 前端代码中按值语义正确选型写出类型不丢失、行为可预测的选择控件。从值与交互契约出发选型Selection 文档 开篇给出了一条总原则先从值和交互两个维度选择选择原语再通过其公开 API 完整保留调用方的领域值类型。这不是样式层面的选择而是数据流层面的决策——选错原语往往意味着值语义单选/多选、持久字段/视图模式、开放文本/封闭集合与业务模型错位。文档给出的六类原语及其适用场景如下RadioGroup从一组可见选项中选择一个持久字段值。每个Radio或RadioItem必须隶属于一个组禁止渲染游离的、不在组内的 radio。SegmentedControl选择一个模式、过滤器或视图。它遵循 radio-group 语义已激活项不能被再次点击取消不可 toggle off、Tab键聚焦到当前选中项、方向键在项之间移动并选中。Tabs选择一个面板提供tablist/tabpanelARIA 语义。Autocomplete接受自由文本可附带建议项。Combobox从可搜索集合中选择一个或多个值且记住所选值。Select从一个封闭、可快速扫视的列表中选取无需文本输入。源码印证SegmentedControl 复用 RadioGroup 内核SegmentedControl 遵循 radio-group 语义在源码中不是文档修辞而是实现事实。SegmentedControl 实现 中SegmentedControl本身就是 Base UIRadioGroup的薄包装function SegmentedControlValue string({ className, ...props }: SegmentedControlPropsValue) { return ( BaseRadioGroupValue className{cn( inline-flex items-center gap-px rounded-[10px] bg-components-segmented-control-bg-normal p-0.5, className, )} {...props} / ) }而SegmentedControlItem内部渲染的是BaseRadio.Root源码 L47-L64。这解释了为什么它能天然获得选中项不可取消、Tab 键进入选中项、方向键移动并选中的行为——这些行为由底层 radio-group 内核统一保证。此外SegmentedControl还通过SegmentedControlSelectionProps强制value与defaultValue二选一源码 L10-L18在类型层面杜绝受控/非受控状态混用。Tabs 实现 则更薄Tabs BaseTabs.Root直接透传TabsList/TabsTab/TabsPanel/TabsIndicator仅叠加 Dify 设计令牌的样式类语义完全继承 Base UI 的tablist/tabpanel。多选 Combobox 的 chips 组合方式文档特别指出多选 Combobox 遵循 Base UI 的 chips 组合模式chips 与输入框共享同一个 input groupchips 可换行wrap整个组随内容垂直生长。在 Combobox 源码 中可以看到对应实现ComboboxChips使用flex flex-wrap items-centerComboboxChip使用inline-flex ... rounded-md渲染单个选中项并与ComboboxChipRemove配合实现逐项移除。Radio 家族解剖与单入口导入文档对Radio家族的用法给出了明确的分工规则使用Radio表示默认外观的 radio当自定义内容本身就构成一个 radio item时使用RadioItem并在其内部放置RadioControl作为 Dify UI 的视觉指示器RadioControl是视觉部件不是独立的 radio不能脱离上下文单独使用。从 RadioGroup 源码 看这一分工有清晰的类型边界RadioGroupValue string包装 Base UIRadioGroup附加flex items-center gap-2基础布局L10-L16RadioItemValue直接映射BaseRadio.Root是容器角色L18-L24RadioControl映射BaseRadio.Indicator通过data-checked:border-[5px]、data-disabled:*等状态类实现选中/禁用态的视觉L33-L52Radio则省略了childrenOmitRadioItemPropsValue, children保证它只能作为无内容的纯指示点使用L54-L74。该模块还额外导出RadioSkeletonL78-L85用于加载占位。文档要求从唯一的公开子路径导入完整家族import { Radio, RadioControl, RadioGroup, RadioItem } from langgenius/dify-ui/radio-group这与 package.json 中的exports声明一致./radio-group子路径同时指向类型与运行时入口。组件库 README 强调包内刻意没有根 barrel 导出所有原语都必须通过各自的公开子路径导入./select、./combobox、./autocomplete等这保证了各组件模块边界的稳定性与按需加载。类型化值绝不把领域值拓宽为string这是 Selection 文档最核心的契约不要把领域值拓宽为string。对于枚举、联合类型、布尔、数字、对象、可空占位值应使用SelectValue, Multiple、RadioGroupValue、RadioValue、RadioItemValue。根泛型的作用范围与JSX 子边界问题根组件的泛型负责约束value、defaultValue以及依赖值的回调。但文档点出一个容易被忽略的 TypeScript 机制JSX children 不会继承父组件的泛型因此对于独立消费的解剖部件当其值无法在局部被推断时需要独立标注类型RadioGroupPromptMode value{promptMode} onValueChange{setPromptMode} RadioPromptMode value{PROMPT_MODE.default} / RadioItemPromptMode value{PROMPT_MODE.custom} RadioControl / Custom prompt /RadioItem /RadioGroup注意Radio和RadioItem上都重复标注了PromptMode——这正是独立消费的体现RadioControl位于RadioItem内部是视觉部件不参与值类型传播而Radio/RadioItem各自直接接收valueprop类型需要就地声明。Select与Combobox字面multiple类型必须匹配运行时模式文档规则ComboboxSubject, true multiple——泛型第二参数的字面量必须与实际运行时multiple属性一致。同时值显示部件在尚未选择时仍可能收到null渲染函数必须处理空值ComboboxSubject, true multiple value{subjects} onValueChange{setSubjects} ComboboxValueSubject, true {(selected) selected?.map((subject) subject.name).join(, ) ?? Anyone} /ComboboxValue ComboboxListSubject {(subject) ComboboxItem value{subject}{subject.name}/ComboboxItem} /ComboboxList /Combobox源码印证了这一契约的设计意图。Combobox 的 props 类型 通过条件类型强制声明多选就必须传multipletype ComboboxPropsValue, Multiple extends boolean | undefined false BaseCombobox.Root.Props Value, Multiple ([Multiple] extends [true] ? { multiple: true } : unknown)而ComboboxSelectedValue类型L35-L37直接编码了多选得到数组、单选得到单值、且都可为null的完整联合type ComboboxSelectedValueValue, Multiple extends boolean | undefined false | (Multiple extends true ? Value[] : Value) | nullSelect有完全同构的约束Select 源码 L19-L35SelectValue的 children 回调签名同样以SelectSelectedValueValue, Multiple为参数。三条补充类型规则AutocompleteList遵循同样规则。Autocomplete 实现 为分组AutocompleteGroupedProps与扁平AutocompleteFlatProps两种items形态定义了重载分组时根、组、项共享同一 item 类型组内可从items本地推断类型但嵌套的Collection是独立的 JSX 边界需要自行标注泛型对应 ComboboxCollection 中children: (item: Value, index: number) React.ReactNode的签名形态。动态multiple{condition}会产生单选/多选联合类型——类型系统按字面boolean而非true收窄回调内需要用分支处理两种形态。优先使用 Base UI 的items集合模式让根组件、值显示、项列表共享同一个运行时数据源只在真正的序列化边界处才把值转成字符串例如提交 API 前。这保证了领域对象如{ id, name }在整条 UI 数据流中不被降级。CheckboxGroup的例外string[]文档同时说明CheckboxGroup遵循 Base UI 上游契约值类型为string[]。如需更强的业务 ID 区分如数字主键应在领域边界建模转换而非假设原语支持更强类型。这与 CheckboxGroup 源码 的极简包装直接透传BaseCheckboxGroup无泛型参数完全吻合。弹出层宽度契约--anchor-width与--available-width文档对Autocomplete、Combobox、Select三类弹出组件立下硬性约束弹出层使用 Base UI 的--anchor-width与--available-widthCSS 变量跟随触发器同时向视口收敛clamp。不要用固定宽度或未经收敛的最小宽度替换该尺寸策略。三个组件的源码均落实了同一条 CSS 尺寸规则——宽度等于锚点宽度但最大不超过可用视口宽度Combobox 弹出层源码 L72-L74w-(--anchor-width) max-w-[min(28rem,var(--available-width))]Autocomplete 弹出层源码 L65-L67w-(--anchor-width) max-w-[min(28rem,var(--available-width))]Select 弹出层源码 L159-L170则对下拉菜单形态略作调整取锚点宽度与可用宽度中较小者作为最小宽度同时以--available-width为最大宽度max-w-(--available-width) min-w-[min(var(--anchor-width),var(--available-width))]列表高度同样受--available-height约束如 Combobox 列表的max-h-[min(20rem,var(--available-height))]L76-L79。这套契约的意义在于弹出层在窄屏幕、侧边栏面板等受限容器内不会溢出视口同时不丢失与触发器等宽的扫视体验。自定义包装或覆盖className时应避免删除这些宽度令牌。测试用例如何锁定这些契约组件库用测试用例将上述类型与交互契约固化为可执行断言。RadioGroup 测试 中有一个专门的类型示例块用ts-expect-error证明boolean 泛组的 radio 项不接受字符串值RadioGroupboolean value{true} onValueChange{() {}} Radioboolean value{true} / RadioItemboolean value{false} / {/* ts-expect-error boolean radio items should not accept string values */} Radioboolean valuetrue / /RadioGroup同类测试还验证了受控单选行为点击后aria-checked状态在项之间正确迁移L61-L89以及RadioGroup与 Dify UI 的Field/Fieldset组合时标签语义不丢失radiogrouprole 可被辅助技术按名称寻址。实践速查表场景首选原语关键契约表单中的持久单选字段RadioGroupValueRadio每个项必须属于组不得游离渲染自定义行内选项卡整行可点RadioItemValue内嵌RadioControlRadioControl仅是视觉部件模式 / 过滤 / 视图切换SegmentedControlValue不可 toggle offTab 进入选中项方向键移动并选中面板切换Tabs保留tablist/tabpanel语义自由文本 建议AutocompleteAutocompleteList需独立标注泛型可搜索单/多选集合ComboboxValue, Multiple字面multiple类型匹配运行时值显示处理null封闭列表快速选取SelectValue, Multiple弹出层宽度遵循--anchor-width/--available-width配套文档可继续参考组件库的 README公开子路径与跨组件契约索引、表单契约、样式契约、可访问性命名契约 与 测试与开发指南。适用前提本文基于当前仓库packages/dify-ui的实际实现该包为 pnpm workspace 私有包langgenius/dify-ui依赖 Base UI 无头组件与 Tailwind 设计令牌行为与版本以本仓库代码为准上游 Base UI 的通用行为细节不在本文仓库证据范围内。【免费下载链接】difyBuild Agentic workflows, RAG pipelines, with rich AI model and tool support on one collaborative workspace. Deploy on cloud, VPC, or self-hosted, so teams move from prototype to production without rebuilding the stack.项目地址: https://gitcode.com/GitHub_Trending/di/dify创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考