拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Material UI Radio Group 单选组件实战:Radio、RadioGroup 与 useRadioGroup 深入指南

Material UI Radio Group 单选组件实战Radio、RadioGroup 与 useRadioGroup 深入指南【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui本文以 Material UIMUI的 Radio Group单选按钮组组件文档为主体覆盖其全部使用场景分组单选、水平布局、受控/非受控模式、独立使用、尺寸与颜色定制、标签位置、错误状态、深度定制以及useRadioGroupHook并结合packages/mui-material中 RadioGroup 与 Radio 的源码实现讲清每个行为背后的机制帮助你在表单开发中正确、无障碍地落地单选交互。何时使用 Radio GroupMUI 文档给出的选型原则非常明确Radio Group 用于让用户从一组选项中选取一个且适用场景是用户需要看到全部可选项如果选项可以折叠收起应优先考虑Select 组件它占用更少的屏幕空间单选按钮组应当默认选中那个最常用的选项减少用户操作成本。组件对应的 Material Design 规范是 Selection Controls 中的 Radio Buttons 部分无障碍规范遵循 W3C WAI-ARIA APG 的 radio 模式roleradiogroup容器 各input[typeradio]子项。文档中Checkboxes vs. Radio Buttons一节也提示复选框表达多选单选按钮表达互斥选择二者不要混用。相关实现源码入口为 RadioGroup 目录 和 Radio 目录本文的源码分析均基于该仓库当前版本。基本用法RadioGroup 分组单选RadioGroup是包裹多个Radio的分组容器它提供两样东西更易用的 API和正确的键盘可访问性浏览器原生 radio 组在共享name后支持方向键切换RadioGroup通过统一注入name保证这一点。标准结构由 5 个组件构成FormControl表单控件容器→FormLabel组标签→RadioGroup分组→FormControlLabel为每个选项绑定 label→Radio控件本身。完整示例来自 RadioButtonsGroup 演示import * as React from react; import Radio from mui/material/Radio; import RadioGroup from mui/material/RadioGroup; import FormControlLabel from mui/material/FormControlLabel; import FormControl from mui/material/FormControl; import FormLabel from mui/material/FormLabel; export default function RadioButtonsGroup() { const id React.useId(); return ( FormControl FormLabel id{${id}-label}Gender/FormLabel RadioGroup aria-labelledby{${id}-label} defaultValuefemale nameradio-buttons-group FormControlLabel valuefemale control{Radio /} labelFemale / FormControlLabel valuemale control{Radio /} labelMale / FormControlLabel valueother control{Radio /} labelOther / /RadioGroup /FormControl ); }注意两个细节aria-labelledby把组标签关联到FormLabel满足 WAI-ARIA 对 radiogroup 的命名要求defaultValuefemale体现了默认选中常用项的原则。源码视角RadioGroup 做了什么从 RadioGroup.js 的源码结构看渲染为带roleradiogroup的FormGroupL83-L94FormGroup还负责在row属性下切换 flex 布局。用useControlled管理选中值L40-L44传入value时为受控模式未传时回退到defaultValue这就是同一组件同时支持两种模式的原因。通过RadioGroupContext向所有子级 Radio 广播上下文L68-L81上下文包含三个字段name所有子 Radio 共享的 name保证互斥onChange子项变更时先setValueState(event.target.value)再调用外部onChange(event, event.target.value)value当前选中值。name支持省略const name useId(nameProp)L66不传时回退为随机生成的 id——类型定义中明确写着 If you dont provide this prop, it falls back to a randomly generated name。通过actions私有 ref 暴露focus()方法L46-L62优先聚焦已选中且未禁用的input找不到则聚焦第一个未禁用的input供FormControl等父组件在表单校验时把焦点带到组内。RadioGroup继承自FormGroup的 propsrow等并在 RadioGroup.d.ts 中声明了自身 propsdefaultValue、name、onChange、value其中onChange的签名为(event: React.ChangeEventHTMLInputElement, value: string) void回调的第二个参数直接是新选中的 value比从event.target.value里手动取值更省事。相关测试位于 RadioGroup.test.js可进一步查看各行为的断言。水平布局row 属性默认单选项纵向排列。传入布尔值row属性即可改为水平排布示例来自 RowRadioButtonsGroup 演示RadioGroup row aria-labelledby{${id}-label} namerow-radio-buttons-group FormControlLabel valuefemale control{Radio /} labelFemale / FormControlLabel valuemale control{Radio /} labelMale / FormControlLabel valueother control{Radio /} labelOther / FormControlLabel valuedisabled disabled control{Radio /} labelother / /RadioGroup从源码看row并非 RadioGroup 自己实现布局而是透传给内部的FormGroup其样式将 flex 方向设为row同时useUtilityClasses会在row为真时给根节点附加MuiRadioGroup-row工具类RadioGroup.js L13-L21便于按类名做样式覆盖。受控模式value 与 onChange把选中状态提升到外部 state即受控用法ControlledRadioButtonsGroup 演示export default function ControlledRadioButtonsGroup() { const id React.useId(); const [value, setValue] React.useState(female); const handleChange (event: React.ChangeEventHTMLInputElement) { setValue((event.target as HTMLInputElement).value); }; return ( FormControl FormLabel id{${id}-label}Gender/FormLabel RadioGroup aria-labelledby{${id}-label} namecontrolled-radio-buttons-group value{value} onChange{handleChange} FormControlLabel valuefemale control{Radio /} labelFemale / FormControlLabel valuemale control{Radio /} labelMale / /RadioGroup /FormControl ); }受控与非受控的切换逻辑集中在 RadioGroup.js 的useControlled调用中valueProp有值时以外部 state 为准、内部setValueState只更新内部镜像无值时完全由defaultValue与组内交互驱动。onChange触发时上下文中的onChange会先把event.target.value写入选中态再回调外部函数因此受控组件中你在handleChange里拿到的event.target.value总是将要/已经被选中的值。独立使用 Radio不使用 RadioGroupRadio可以不依赖RadioGroup单独使用此时需要自行保证name一致浏览器据此互斥、checked与value正确RadioButtons 演示export default function RadioButtons() { const [selectedValue, setSelectedValue] React.useState(a); const handleChange (event: React.ChangeEventHTMLInputElement) { setSelectedValue(event.target.value); }; return ( div Radio checked{selectedValue a} onChange{handleChange} valuea nameradio-buttons slotProps{{ input: { aria-label: A } }} / Radio checked{selectedValue b} onChange{handleChange} valueb nameradio-buttons slotProps{{ input: { aria-label: B } }} / /div ); }这里有一个值得注意的实现细节Radio.js 在检测到处于RadioGroup上下文时会自动把checked与name从上下文推导出来L163-L176而比较选中值时使用的是areEqualValuesL112-L119——对基本类型做String(a) String(b)字符串化比较注释说明DOM 最终会把 value 转成字符串。也就是说即使你传入数字类型的 value组内选中判断也能正确工作但若 value 是对象则要求引用相等a b。尺寸size 属性与图标字号修改size属性即可改变单选按钮大小也可以直接定制内部 SVG 图标的字号SizeRadioButtons 演示const controlProps (item: string) ({ checked: selectedValue item, onChange: handleChange, value: item, name: size-radio-button-demo, inputProps: { aria-label: item }, }); div Radio {...controlProps(a)} sizesmall / Radio {...controlProps(b)} / Radio {...controlProps(c)} sx{{ .MuiSvgIcon-root: { fontSize: 28, }, }} / /div从源码看size的默认值是mediumRadio.js L133small/large时根节点会附加MuiRadio-sizeSmall/MuiRadio-sizeLarge类且size会作为fontSize克隆注入到icon与checkedIcon两个 SVG 图标上L200-L203icon: React.cloneElement(icon, { fontSize: icon.props.fontSize ?? size }), checkedIcon: React.cloneElement(checkedIcon, { fontSize: checkedIcon.props.fontSize ?? size, }),因此当你自己传入自定义图标时若图标未显式设置fontSize它会自动跟随size属性缩放——这解释了为什么改图标字号与改 size是两种等价但作用层级不同的方案。颜色color 属性与主题调色板color属性接受主题调色板中的任意颜色名含自定义色默认primaryColorRadioButtons 演示Radio {...controlProps(a)} / Radio {...controlProps(b)} colorsecondary / Radio {...controlProps(c)} colorsuccess / Radio {...controlProps(d)} colordefault / Radio {...controlProps(e)} sx{{ color: pink[800], .Mui-checked: { color: pink[600], }, }} /Radio.js 的RadioRoot样式中组件启动时会遍历theme.palette的所有颜色键为每种颜色动态生成两个 varianthover 态背景palette[color].main叠加action.hoverOpacity的透明度与选中态颜色.Mui-checked { color: palette[color].main }L73-L95。所以只要你在主题中注册了新颜色colormyCustomColor就能直接生效无需额外样式。标签位置labelPlacement每个选项的文案位置由FormControlLabel的labelPlacement属性控制取值为start默认标签在右侧、end、top、bottomFormControlLabelPlacement 演示RadioGroup row aria-labelledby{${id}-label} nameposition defaultValuetop FormControlLabel valuebottom control{Radio /} labelBottom labelPlacementbottom / FormControlLabel valueend control{Radio /} labelEnd / /RadioGroup显示错误状态error 与 FormHelperText文档建议单选组默认就应有选中值若业务上允许暂不选择则应在表单提交时显示错误。实现方式是给FormControl传error属性会把错误态通过上下文传给组内控件并用FormHelperText展示提示文案ErrorRadios 演示export default function ErrorRadios() { const id React.useId(); const [value, setValue] React.useState(); const [error, setError] React.useState(false); const [helperText, setHelperText] React.useState(Choose wisely); const handleRadioChange (event: React.ChangeEventHTMLInputElement) { setValue(event.target.value); setError(false); setHelperText(Choose wisely); }; const handleSubmit (event: React.FormEventHTMLFormElement) { event.preventDefault(); if (value best) { setHelperText(You got it!); setError(false); } else if (value worst) { setHelperText(Sorry, wrong answer!); setError(true); } else { setHelperText(Please select an option.); setError(true); } }; return ( form onSubmit{handleSubmit} FormControl sx{{ m: 3 }} error{error} variantstandard FormLabel id{${id}-label}Pop quiz: MUI is…/FormLabel RadioGroup aria-labelledby{${id}-label} namequiz value{value} onChange{handleRadioChange} FormControlLabel valuebest control{Radio /} labelThe best! / FormControlLabel valueworst control{Radio /} labelThe worst. / /RadioGroup FormHelperText{helperText}/FormHelperText Button sx{{ mt: 1, mr: 1 }} typesubmit variantoutlined Check Answer /Button /FormControl /form ); }错误色的来源可以在 radioGroupClasses.ts 与 RadioGroup.js 中找到error为真时根节点附加MuiRadioGroup-error类useUtilityClasses中的error error分支对应样式会把组内单选按钮的主色切换为主题调色板中error色的main。disabled同理Radio 未显式传disabled时会从FormControl上下文继承禁用态Radio.js L142-L152。深度定制替换图标与样式覆盖文档指出定制组件的完整体系见 customization 文档目录。针对 Radio最典型的定制手段是替换icon/checkedIcon默认分别是RadioButtonIcon /与RadioButtonIcon checked /见 RadioButtonIcon.js 与 Radio.js L121-L122同时配合disableRipple去掉点击涟漪得到完全不同的视觉风格CustomizedRadios 演示const BpIcon styled(span)(({ theme }) ({ borderRadius: 50%, width: 16, height: 16, boxShadow: inset 0 0 0 1px rgba(16,22,26,.2), inset 0 -1px 0 rgba(16,22,26,.1), backgroundColor: #f5f8fa, backgroundImage: linear-gradient(180deg,hsla(0,0%,100%,.8),hsla(0,0%,100%,0)), input:hover ~ : { backgroundColor: #ebf1f5, }, input:disabled ~ : { boxShadow: none, background: rgba(206,217,224,.5), }, })); const BpCheckedIcon styled(BpIcon)({ backgroundColor: #137cbd, ::before: { display: block, width: 16, height: 16, backgroundImage: radial-gradient(#fff,#fff 28%,transparent 32%), content: , }, }); function BpRadio(props: RadioProps) { return ( Radio disableRipple colordefault checkedIcon{BpCheckedIcon /} icon{BpIcon /} {...props} / ); } export default function CustomizedRadios() { const id React.useId(); return ( FormControl FormLabel id{${id}-label}Gender/FormLabel RadioGroup defaultValuefemale aria-labelledby{${id}-label} namecustomized-radios FormControlLabel valuefemale control{BpRadio /} labelFemale / FormControlLabel valuemale control{BpRadio /} labelMale / FormControlLabel valueother control{BpRadio /} labelOther / FormControlLabel valuedisabled disabled control{BpRadio /} label(Disabled option) / /RadioGroup /FormControl ); }该演示刻意用原生 CSS 选择器input:hover ~ 、input:disabled ~ 响应输入框状态并处理了forced-colors系统高对比模式场景说明自定义图标时应当把 hover / disabled / 高对比模式的视觉也考虑进去。useRadioGroup读取父级单选组上下文高级定制场景下MUI 暴露了useRadioGroup()Hook它返回父级 RadioGroup 的上下文值Radio组件内部也是用它实现的。API 与示例import { useRadioGroup } from mui/material/RadioGroup;返回值valueobject包含value.namestring可选用于引用该组选中值的 namevalue.onChangefunc可选某个单选按钮被选中时触发value.valueany可选当前选中单选按钮的值。完整示例UseRadioGroup 演示用它判断当前项是否选中从而给选中项的 label 上色——const StyledFormControlLabel styled((props: StyledFormControlLabelProps) ( FormControlLabel {...props} / ))(({ theme }) ({ variants: [ { props: { checked: true }, style: { .MuiFormControlLabel-label: { color: theme.palette.primary.main, }, }, }, ], })); function MyFormControlLabel(props: FormControlLabelProps) { const radioGroup useRadioGroup(); let checked false; if (radioGroup) { checked radioGroup.value props.value; } return StyledFormControlLabel checked{checked} {...props} /; } export default function UseRadioGroup() { return ( RadioGroup nameuse-radio-group defaultValuefirst MyFormControlLabel valuefirst labelFirst control{Radio /} / MyFormControlLabel valuesecond labelSecond control{Radio /} / /RadioGroup ); }实现非常薄位于 useRadioGroup.ts就是React.useContext(RadioGroupContext)返回RadioGroupContextValue或undefined不在 RadioGroup 内时。这也解释了上文Radio源码中的行为——它先调用useRadioGroup()拿到上下文再据此推导checked、name并把自身onChange与radioGroup.onChange用createChainedFunction串联Radio.js L166使得独立 Radio 的 onChange与组级 onChange可以同时生效。无障碍Accessibility要点文档 Accessibility 一节的完整要求所有表单控件都应有标签单选按钮也不例外。通常通过label元素完成在 MUI 中即使用FormControlLabel包裹每个Radio组的整体命名则通过RadioGroup的aria-labelledby关联FormLabel。无法使用 label 时需要把无障碍属性直接加到 input 上。此时可通过slotProps.input传入aria-label、aria-labelledby、title等Radio valueradioA slotProps{{ input: { aria-label: Radio A }, }} /从源码看还有两点机制值得了解RadioGroup渲染的容器带有roleradiogroup屏幕阅读器可识别这是一组互斥控件actionsref 的focus()优先聚焦已选中项RadioGroup.js L46-L62这保证了表单校验失败后焦点能准确落在组内是键盘与读屏体验的一部分。小结与延伸阅读本文完整继承了 MUI 官方 Radio Group 文档的全部要点——组合作用、row布局、受控模式、独立 Radio、尺寸、颜色、标签位置、错误状态、深度定制、useRadioGroupHook 与无障碍规范并补充了源码层面的佐证组件实现RadioGroup.js、RadioGroup.d.ts、RadioGroupContext.ts、useRadioGroup.ts、radioGroupClasses.ts、Radio.js、RadioButtonIcon.js、radioClasses.ts全部官方演示源码radio-buttons 演示目录RadioButtonsGroup、RowRadioButtonsGroup、ControlledRadioButtonsGroup、RadioButtons、SizeRadioButtons、ColorRadioButtons、FormControlLabelPlacement、ErrorRadios、CustomizedRadios、UseRadioGroup等。掌握这些后你可以直接在任何 React 表单中组合出符合 Material Design 与 WAI-ARIA 规范、并经过受控/非受控两种模式验证的单选交互。【免费下载链接】material-uiMaterial UI: Comprehensive React component library that implements Googles Material Design. Free forever.项目地址: https://gitcode.com/GitHub_Trending/ma/material-ui创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门