Ant Design Radio.Group 互斥单选框组:基本用法、配置参数与源码级原理解析
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design
导读
在 Ant Design(antd)中,单个Radio只是一个可勾选的圆圈,真正让多个单选形成「一组互斥选项」的,是Radio.Group容器。本指南以 components/radio/demo/radiogroup.md 中「一组互斥的 Radio 配合使用」这一核心语义为骨架,完整讲解Radio.Group的受控用法、options声明式配置、按钮形态、禁用与尺寸等全部配置项,并结合仓库源码剖析其「受控/非受控状态管理」与「Context 广播选中值」的底层实现。读完本文,你将能够熟练使用Radio.Group构建任何互斥选择场景,并理解其内部工作机制。
一、互斥的本质:为什么单选必须放进 Radio.Group
radiogroup.md的说明非常凝练——zh-CN 为「一组互斥的 Radio 配合使用」,en-US 为 "A group of radio components"。这句话点出了 Radio 组件体系中最核心的设计:单选互斥不是每个Radio自己实现的,而是由外层Radio.Group统一协调的。
从 radio.tsx 的源码可以看到,单个Radio内部通过React.useContext(RadioGroupContext)读取组上下文,并在点击时依次触发自身props.onChange与组上下文中的groupContext.onChange:
const onChange = (e: RadioChangeEvent) => { props.onChange?.(e); groupContext?.onChange?.(e); };也就是说,选中状态是「组」级别的共享状态:组内任意一个Radio被点击,事件会上报到Radio.Group,由组更新当前值,再通过 Context 广播给组内所有Radio,让它们重新计算各自的checked(见 radio.tsx):
if (groupContext) { radioProps.name = groupContext.name; radioProps.onChange = onChange; radioProps.checked = props.value === groupContext.value; radioProps.disabled = radioProps.disabled ?? groupContext.disabled; }checked = props.value === groupContext.value这一行就是「互斥」的底层逻辑:同一时刻组内只有一个Radio的value与组当前值相等,因此永远只选中一项。
二、基本用法:受控模式下的一组互斥 Radio
radiogroup.md对应的演示代码 components/radio/demo/radiogroup.tsx 给出了最经典、最完整的受控用法,这也是本文所有例子的基础。下面完整展开并逐点注释:
import React, { useState } from 'react'; import type { RadioChangeEvent } from 'antd'; import { Radio } from 'antd'; const App: React.FC = () => { // 用 useState 维护当前选中的值,默认选中 value 为 1 的 Radio const [value, setValue] = useState(1); // 组内任意 Radio 被点击都会触发 onChange,e.target.value 即被点击项的 value const onChange = (e: RadioChangeEvent) => { console.log('radio checked', e.target.value); setValue(e.target.value); }; return ( <Radio.Group onChange={onChange} value={value}> <Radio value={1}>A</Radio> <Radio value={2}>B</Radio> <Radio value={3}>C</Radio> <Radio value={4}>D</Radio> </Radio.Group> ); }; export default App;要点说明:
value+onChange构成受控模式:value决定当前选中项,onChange在用户点击时被回调。用户点击 A/B/C/D 中任意一项,e.target.value就是该项的value,将其setValue回去即完成受控更新。value可以是任意类型(数字、字符串等),只要与各Radio的value一一对应即可;本例中value是数字1/2/3/4。- 互斥效果开箱即用:不需要给每个
Radio手动设置checked,组会统一处理。 - 如果你希望初始有一个默认选中项,且后续不关心受控更新,可以改用
defaultValue(详见下文 API 表)。
相关演示的完整清单
仓库 components/radio/demo 目录围绕「Radio 组」场景提供了成体系的演示,均可作为实战参考:
| 演示文件 | 主题 |
|---|---|
| radiogroup.tsx | 一组互斥 Radio 的基本用法(本文主体) |
| radiogroup-options.tsx | 使用options数组声明式渲染选项 |
| radiogroup-more.tsx | 组内嵌入「更多…」输入框等自定义内容 |
| radiogroup-with-name.tsx | 为组内所有 Radio 统一设置原生name |
| radiobutton.tsx | 按钮风格的单选组 |
| radiobutton-solid.tsx | 实心(solid)按钮风格 |
| disabled.tsx | 禁用状态 |
| size.tsx | 三种尺寸切换 |
三、options属性:用数组声明式渲染一组选项
除了把Radio作为children手写,Radio.Group还支持通过options属性声明式渲染,代码更简洁,也便于从后端数据直接驱动。演示代码 components/radio/demo/radiogroup-options.tsx 展示了全部三种写法:
import React, { useState } from 'react'; import type { RadioChangeEvent } from 'antd'; import { Radio } from 'antd'; // 写法一:纯字符串数组 const plainOptions = ['Apple', 'Pear', 'Orange']; // 写法二:对象数组(label + value,可附加 title 等) const options = [ { label: 'Apple', value: 'Apple' }, { label: 'Pear', value: 'Pear' }, { label: 'Orange', value: 'Orange', title: 'Orange' }, ]; // 写法三:对象数组 + 单项 disabled const optionsWithDisabled = [ { label: 'Apple', value: 'Apple' }, { label: 'Pear', value: 'Pear' }, { label: 'Orange', value: 'Orange', disabled: true }, ]; const App: React.FC = () => { const [value1, setValue1] = useState('Apple'); const [value2, setValue2] = useState('Apple'); const [value3, setValue3] = useState('Apple'); const [value4, setValue4] = useState('Apple'); const onChange1 = ({ target: { value } }: RadioChangeEvent) => { console.log('radio1 checked', value); setValue1(value); }; // onChange2 / onChange3 / onChange4 结构相同,此处省略 return ( <> {/* 字符串数组 */} <Radio.Group options={plainOptions} onChange={onChange1} value={value1} /> <br /> {/* 对象数组,其中一项 disabled */} <Radio.Group options={optionsWithDisabled} onChange={onChange2} value={value2} /> <br /> <br /> {/* 按钮形态(outline) */} <Radio.Group options={options} onChange={onChange3} value={value3} optionType="button" /> <br /> <br /> {/* 按钮形态 + 实心样式 */} <Radio.Group options={optionsWithDisabled} onChange={onChange4} value={value4} optionType="button" buttonStyle="solid" /> </> ); }; export default App;options支持的数据形态
结合 group.tsx 的源码实现,options的解析逻辑非常清晰:
string | number基本类型数组:每一项直接作为该Radio的value和显示文本,例如['Apple', 'Pear', 'Orange']。- 对象数组:支持以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
label | ReactNode | 选项显示文本 |
value | any | 选项值,用于互斥比较与onChange回调 |
disabled | boolean | 仅禁用该选项(优先级与组级disabled合并,见下) |
title | string | 选项的原生title提示 |
style | CSSProperties | 作用于该选项的样式 |
id | string | 该选项的原生id |
required | boolean | 该选项的原生required标记 |
源码中对象形态的渲染逻辑(group.tsx)为:
return ( <Radio key={`radio-group-value-options-${option.value}`} prefixCls={prefixCls} disabled={option.disabled || disabled} value={option.value} checked={value === option.value} title={option.title} style={option.style} id={option.id} required={option.required} > {option.label} </Radio> );注意disabled={option.disabled || disabled}这行:单项disabled与组级disabled是「或」的关系,组级禁用后即使单项未声明也会被禁用;反之,组未禁用时,可以通过单项disabled: true单独禁用一个选项(如上面的optionsWithDisabled中 Orange 不可选)。
四、进阶场景:按钮形态、禁用、尺寸与自定义内容
1. 按钮形态optionType="button"与实心buttonStyle="solid"
单选组有两种展示形态:默认的「圆点 + 文本」和「按钮」形态。通过optionType="button"切换为按钮组,再配合buttonStyle选择描边(outline,默认)或实心(solid)样式:
<Radio.Group defaultValue="a" optionType="button" buttonStyle="solid"> <Radio.Button value="a">Hangzhou</Radio.Button> <Radio.Button value="b">Shanghai</Radio.Button> <Radio.Button value="c">Beijing</Radio.Button> </Radio.Group>Radio.Group上设置optionType="button"时,组内Radio会渲染为按钮样式;等价地,也可以使用复合组件Radio.Button显式声明,其实现(radioButton.tsx)正是通过RadioOptionTypeContextProvider把optionType注入为'button'。- 样式切换的底层逻辑在 radio.tsx:
optionType === 'button'时,prefixCls从ant-radio变为ant-radio-button,从而命中按钮形态的样式与波纹效果。 - 注意:
optionType仅在Radio.Group上受支持,直接在单个Radio上使用optionType会在开发环境触发 antd 的 usage 警告(见 radio.tsx)。
2. 禁用:组级禁用与单项禁用
// 整组禁用 <Radio.Group defaultValue="a" disabled> <Radio value="a">A</Radio> <Radio value="b">B</Radio> </Radio.Group> // 仅禁用一个选项(options 写法) <Radio.Group defaultValue="a" options={[{ label: 'A', value: 'a' }, { label: 'B', value: 'b', disabled: true }]} />从源码看,disabled有三层来源并依次合并:单项props.disabled→ 组上下文groupContext.disabled(由Radio.Group的disabled提供)→ 全局DisabledContext(由ConfigProvider disabled或Form表单项注入),见 radio.tsx。
3. 尺寸size
Radio.Group的size接受large/middle/small,不传时通过useSize自动继承ConfigProvider的全局size配置(group.tsx),并生成ant-radio-group-large/middle/small修饰类(group.tsx)。
<Radio.Group defaultValue="a" size="large"> <Radio.Button value="a">Hangzhou</Radio.Button> <Radio.Button value="b">Shanghai</Radio.Button> </Radio.Group>4. 组内嵌入自定义内容:动态「更多…」输入框
演示 radiogroup-more.tsx 展示了一个实用的组合技巧——当选中「More...」时动态渲染一个输入框:
const [value, setValue] = useState(1); <Radio.Group onChange={onChange} value={value}> <Space direction="vertical"> <Radio value={1}>Option A</Radio> <Radio value={2}>Option B</Radio> <Radio value={3}>Option C</Radio> <Radio value={4}> More... {value === 4 ? <Input style={{ width: 100, marginInlineStart: 10 }} /> : null} </Radio> </Space> </Radio.Group>由于Radio的children只是普通内容插槽(见 radio.tsx 的<span>{children}</span>),在组内混入Input、Space等任意内容是完全合法的,这为「其他」选项 + 补充输入框这类常见交互提供了优雅的解法。
五、Radio.GroupAPI 速查(源自 interface.ts)
以下属性均定义在 components/radio/interface.ts 的RadioGroupProps中,是官方支持的完整配置面:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
value | any | - | 受控值,指定当前选中的Radio的value |
defaultValue | any | - | 非受控模式下的初始选中值 |
onChange | (e: RadioChangeEvent) => void | - | 选项变化时的回调,e.target.value为新选中值 |
disabled | boolean | false | 是否禁用整组 |
size | 'large' \| 'middle' \| 'small' | 继承ConfigProvider | 组尺寸,仅对按钮形态外观有明显影响 |
name | string | - | 为组内所有Radio统一设置原生name,便于表单提交与原生互斥兜底 |
options | (string \| number \| { label; value; disabled?; title?; style?; id?; required? })[] | - | 以数组声明式渲染选项 |
optionType | 'default' \| 'button' | 'default' | 展示形态:圆点单选或按钮单选 |
buttonStyle | 'outline' \| 'solid' | 'outline' | 按钮形态下的样式(描边 / 实心) |
id | string | - | 容器的原生id |
onMouseEnter/onMouseLeave/onFocus/onBlur | 事件回调 | - | 容器级事件透传(group.tsx) |
此外,Radio.Group容器会自动透传aria-*与data-*属性(源码中通过pickAttrs(props, { aria: true, data: true })实现,见 group.tsx),并原生支持 RTL 布局(direction === 'rtl'时添加ant-radio-group-rtl类)。
受控与非受控:useMergedState的统一
从 group.tsx 可见,Radio.Group内部通过useMergedState(props.defaultValue, { value: props.value })管理选中值:传了value即为受控模式,组值完全由外部驱动;只传defaultValue则为非受控模式,组内部自维护状态。
事件处理onRadioChange(group.tsx)的关键逻辑是:
const onRadioChange = (ev: RadioChangeEvent) => { const lastValue = value; const val = ev.target.value; if (!('value' in props)) { setValue(val); // 非受控时,内部同步状态 } const { onChange } = props; if (onChange && val !== lastValue) { onChange(ev); // 仅在选中值真正变化时回调 } };值得注意的细节:只有值发生变化(val !== lastValue)才会触发onChange,重复点击当前选中项不会产生冗余回调,这与原生 radio 的行为保持一致。
六、源码级原理:状态如何从「组」广播到「项」
理解Radio.Group的互斥机制,核心是掌握它基于 React Context 的「单向数据流」链路。仓库中的相关实现文件为:
- components/radio/group.tsx:组容器,负责状态管理与广播
- components/radio/context.ts:Context 定义(
RadioGroupContext与RadioOptionTypeContext) - components/radio/radio.tsx:单项,消费组上下文
- components/radio/interface.ts:类型定义
- components/radio/index.tsx:复合组件组装(
Radio.Group、Radio.Button)
一次完整交互的调用链可以归纳为:
- 点击:用户点击组内某个
Radio,底层rc-checkbox触发onChange; - 上报:radio.tsx 先触发单项自身的
onChange,再调用groupContext.onChange(e),把事件交给Radio.Group; - 更新:group.tsx 的
onRadioChange取出e.target.value,在非受控模式下setValue更新组状态(受控模式下状态由外部value驱动),并在值变化时回调props.onChange; - 广播:
Radio.Group通过RadioGroupContextProvider把最新的value、disabled、name、optionType和onChange注入 Context(group.tsx); - 重算:组内每个
Radio重新渲染,按props.value === groupContext.value重新计算自己的checked,于是旧选中项取消、新选中项点亮,互斥效果达成。
同时,context.ts 中还定义了独立的RadioOptionTypeContext,专门用于把「按钮形态」从Radio.Group传给内部Radio(Radio.Button正是通过它注入optionType='button'),实现形态与选中状态的解耦。
七、事件对象RadioChangeEvent
onChange的回调参数RadioChangeEvent(定义于 interface.ts)结构如下:
interface RadioChangeEvent { target: RadioChangeEventTarget; // 包含 value、checked、name 等 Radio 属性 stopPropagation: () => void; preventDefault: () => void; nativeEvent: MouseEvent; }其中target是RadioChangeEventTarget(interface.ts),除了RadioProps的全部属性外,还额外带有checked: boolean。实际开发中最常用的是e.target.value(新选中的值),也可以像演示代码那样用解构简写:
const onChange = ({ target: { value } }: RadioChangeEvent) => { setValue(value); };八、实践要点与注意事项
- 互斥无需手动
checked:不要在组内手动给Radio设checked,组上下文会自动计算;手动设置会与组逻辑冲突。 - 区分
value与defaultValue:需要外部控制(如受表单状态或请求结果驱动)时用value + onChange;仅需初始选中时用defaultValue,二者不要混用。 optionType只写在Radio.Group上:单独写在Radio上会触发开发环境警告;按钮形态也可以直接用Radio.Button复合组件。disabled的叠加规则:单项disabled、组级disabled、ConfigProvider/Form的全局DisabledContext三者取「或」,优先级从内到外依次合并。options与children二选一:源码中options存在且非空时优先渲染options,children会被忽略(group.tsx)。- 原生
name的兜底价值:通过Radio.Group的name属性统一设置后,组内所有Radio共享同一原生name,在无 JavaScript 的极端场景下表单提交也能保持单选语义;配合Form使用时,antd 的FormItemInputContext会自动为Radio添加ant-radio-wrapper-in-form-item修饰类以适配表单项样式。 - 重复点击不触发
onChange:选中值未变化时回调不会被触发,依赖「每次点击都回调」的逻辑需要自行处理。
以上内容均可在当前仓库中逐一验证:演示代码见 components/radio/demo 目录,实现源码见 group.tsx、radio.tsx、radioButton.tsx、context.ts 与 interface.ts。结合这些文件,你可以在自己的项目中放心地把Radio.Group用于任何需要互斥单选的场景。
【免费下载链接】ant-designAn enterprise-class UI design language and React UI library项目地址: https://gitcode.com/gh_mirrors/ant/ant-design
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考