- 前端
- UI组件
【免费下载链接】react-toolbox
A set of React components implementing Google's Material Design specification with the power of CSS Modules
RadioButton(单选按钮)是 Material Design 中"互斥选择"的核心控件。本文将围绕 react-toolbox 仓库中 components/radio/readme.md 展开,完整讲解 RadioGroup 与 RadioButton 的属性 API、受控用法、事件机制、源码级实现原理以及 CSS Modules 主题定制方案。读完本文,你将能在 react-toolbox 项目中正确、熟练地使用单选组件,并深入理解其底层组合工厂与主题系统。
一、什么是 RadioButton,何时该使用它
RadioButton 允许用户从一组选项中选择一个。Material Design 规范建议:当用户需要并排看到所有可选选项、且选择互斥(exclusive selection)时,使用单选按钮;如果选项较多、空间有限,则应改用下拉框(dropdown),因为下拉框比平铺展示所有选项更节省空间。
在 react-toolbox 中,单选按钮必须配合RadioGroup一起使用——单个 RadioButton 无法独立表达"一组互斥选项"的语义。RadioGroup负责维护当前选中值,并把checked、disabled、onChange等状态自动派发给内部每个 RadioButton。
主题注入键:文档给出的主题 key 为ToolboxButton(原文档如此描述);在源码层面,组件实际通过themr(RADIO, theme)注册主题,RADIO常量定义于 components/identifiers.js,默认主题样式来自 components/radio/theme.module.css。
二、快速开始:一个完整可运行的示例
从react-toolbox/lib/radio导入RadioGroup与RadioButton,然后像使用任何受控组件一样维护选中值。以下示例直接取自 components/radio/readme.md:
import { RadioGroup, RadioButton } from 'react-toolbox/lib/radio'; class RadioTest extends React.Component { state = { value: 'vvendetta' }; handleChange = (value) => { this.setState({value}); }; render () { return ( <RadioGroup name='comic' value={this.state.value} onChange={this.handleChange}> <RadioButton label='The Walking Dead' value='thewalkingdead'/> <RadioButton label='From Hell' value='fromhell' disabled/> <RadioButton label='V for a Vendetta' value='vvendetta'/> <RadioButton label='Watchmen' value='watchmen'/> </RadioGroup> ); } }要点解析:
RadioGroup的value是当前选中值,onChange在选中值变化时回调(回调参数为被选中 RadioButton 的value);- 每个
RadioButton通过value标识自身,label为显示文本,disabled可使单个选项禁用; - 示例中
V for a Vendetta的value为vvendetta,与初始state.value一致,因此默认处于选中状态。
仓库 spec/components/radio.js(以及 TypeScript 版本 spec/ts/radio.tsx)中还有一个更完整的演示,额外展示了onFocus/onBlur的用法:
<RadioGroup name="comic" value={this.state.value} onChange={this.handleChange}> <RadioButton label="The Walking Dead" value="thewalkingdead" /> <RadioButton label="From Hell" value="fromhell" disabled /> <RadioButton label="V for a Vendetta" value="vvendetta" onFocus={this.handleFocus} /> <RadioButton label="Watchmen" value="watchmen" onBlur={this.handleBlur} /> </RadioGroup>三、RadioGroup API:容器如何管理选中状态
RadioGroup是单选组的容器。它接收的属性和事件会被有选择地派发给子级,但每个 RadioButton 也可以独立声明自己的行为。
3.1 属性总览(来自原文档)
| Name | Type | Default | Description |
|---|---|---|---|
className | String | '' | 为组添加类,用于自定义样式。 |
disabled | Boolean | false | 为 true 时,整个组以禁用状态显示。 |
name | String | 输入元素组的 name。 | |
onChange | Function | 值变化时被调用的回调函数。 | |
value | Any | 单选组中默认选中的值。 |
3.2 源码实现:状态如何下发给子级
在 components/radio/RadioGroup.js 中,renderRadioButtons()使用React.Children.map遍历子元素,并通过isComponentOfType(来自 components/utils/is-component-of-type.js)判断子级是否为 RadioButton,只对真正的 RadioButton 注入派生属性:
React.Children.map(this.props.children, child => ( !isComponentOfType(RadioButton, child) ? child : React.cloneElement(child, { checked: child.props.value === this.props.value, disabled: this.props.disabled || child.props.disabled, onChange: this.handleChange.bind(this, child.props.value), }) ))由此可以推断出三条关键规则:
checked完全由组决定:child.props.value === this.props.value,选中逻辑与每个按钮自身的checked属性无关;disabled是"或"关系:组禁用或按钮自身禁用,任一为 true 即禁用;onChange被包装:点击某个按钮触发的是handleChange,它把该按钮的value作为第一个参数回调this.props.onChange(value, event),这正是示例中handleChange = (value) => ...能直接拿到值的原因。
非 RadioButton 的子元素(如普通节点)会被原样保留,因此你可以在组内混排说明文字等自定义内容。
3.3 组级禁用
RadioGroup的默认disabled为false。设置为true后,所有子按钮均显示为禁用态(CSS 层面由disabled主题类控制,见第五节)。
四、RadioButton API:单个选项的行为与事件
RadioButton是构成单选组的内部组件,渲染为 HTML<input type="radio">,与它相关的属性会透传给该 input 元素。
4.1 属性总览(来自原文档)
| Name | Type | Default | Description |
|---|---|---|---|
checked | Boolean | false | 为 true 时,input 元素默认被选中。由父级传递。 |
className | String | '' | 为单选按钮添加类,用于自定义样式。 |
disabled | Boolean | false | 为 true 时,该项以禁用状态显示。 |
label | Stringornode | '' | 单选按钮的标签文本。 |
name | String | input 元素的 name。 | |
onBlur | Function | input 失焦时被调用的回调函数。 | |
onChange | Function | 值变化时被调用的回调函数。 | |
onFocus | Function | input 聚焦时被调用的回调函数。 | |
value | Any | 单选按钮的值。 |
4.2 源码实现:点击、聚焦与渲染逻辑
components/radio/RadioButton.js 是核心实现,有几个值得注意的细节:
点击事件处理。组件通过handleClick接管点击逻辑:
handleClick = (event) => { const { checked, disabled, onChange } = this.props; if (event.pageX !== 0 && event.pageY !== 0) this.blur(); if (!disabled && !checked && onChange) onChange(event, this); };它做了两件事:当点击来自真实鼠标(pageX/pageY非 0)时先调用blur()移除焦点光环,然后再在"未禁用且未选中且有回调"的前提下触发onChange(event, this)——注意回调的第二个参数是组件实例本身。
聚焦/失焦代理。focus()与blur()通过ref挂载的inputNode代理到底层 input 元素,从而让onFocus/onBlur能作用在真实的原生 input 上。
渲染结构。render 输出如下 DOM 结构:
<label>const factory = (ripple) => { const Radio = ({ checked, onMouseDown, theme, ...other }) => ( <div >const ThemedRadio = radioFactory(themedRippleFactory({ centered: true, spread: 2.6 })); const ThemedRadioButton = themr(RADIO, theme)(radioButtonFactory(ThemedRadio)); const ThemedRadioGroup = themr(RADIO, theme)(radioGroupFactory(ThemedRadioButton));radioFactory:生成圆点 DOM;themedRippleFactory({ centered: true, spread: 2.6 }):来自 components/ripple,为圆点注入居中、扩散系数为 2.6 的波纹效果;radioButtonFactory:把圆点与原生 input、label 组装成完整按钮;radioGroupFactory:把多个按钮组装成互斥组;- 最外层
themr(RADIO, theme):react-css-themr 的注入函数,把 components/radio/theme.module.css 的类名映射到themeprop。
由此可知完整事件链路为:用户点击 label → 原生 input 的 onClick 触发 handleClick → 若可选中则回调 onChange → RadioGroup 的 handleChange 把该按钮 value 上报给父级 → 父级 setState 更新 group 的 value → cloneElement 重新计算 checked 并触发重渲染。这也是它天然受控的原因。
六、主题定制:Theming 类名与 CSS 结构
react-toolbox 采用 CSS Modules + react-css-themr 实现主题化。通过给themeprop 传入类名映射即可替换任意内部样式。
6.1 可注入的主题类(来自原文档)
| Name | Description |
|---|---|
disabled | 单选按钮禁用时添加到根节点。 |
field | 作为组件的根类使用。 |
input | 用于 input 元素。 |
radio | 用于单选圆点元素。 |
radioChecked | 单选圆点处于选中状态时使用。 |
ripple | 为波纹效果提供样式。 |
text | 用于文本标签元素。 |
对应 TypeScript 声明见 components/radio/RadioButton.d.ts(RadioButtonTheme)与 components/radio/base.d.ts(RadioTheme:radio、radioChecked、ripple)。
6.2 默认主题的视觉实现(源码视角)
components/radio/theme.module.css 展示了单选圆点的绘制思路,主题定制时可以此为参考:
.radio:通过border: calc(0.2 * var(--unit)) solid var(--radio-text-color)与border-radius: 50%画出外圈;内部圆点由::before伪元素实现,默认transform: scale(0)(不可见),并通过transition: transform 0.2s var(--animation-curve-default)平滑过渡;.radioChecked:composes: radio继承外圈样式,同时border-color切换为主题色var(--radio-inner-color),::before通过transform: scale(0.65)显示内部实心圆点;.ripple:波纹颜色为主题色、透明度 0.3,过渡时长 650ms;.disabled:文字、圆点边框、选中圆点内部均切换为禁用色var(--radio-disabled-color),且cursor: auto;.input:原生 input 被绝对定位、尺寸为 0、opacity: 0完全隐藏,但保留了:focus ~ .radio/:focus ~ .radioChecked的兄弟选择器,从而在键盘聚焦时用box-shadow绘制聚焦光环(无障碍友好)。
6.3 可调样式变量
主题变量集中在 components/radio/config.module.css,定制尺寸与配色时可以直接覆盖这些 CSS 变量:
| 变量 | 默认值 | 含义 |
|---|---|---|
--radio-field-margin-bottom | calc(1.5 * var(--unit)) | 每个选项底边距 |
--radio-button-size | calc(2 * var(--unit)) | 圆点直径 |
--radio-inner-color | var(--color-primary) | 选中圆点/内点主题色 |
--radio-focus-color | color-mod(var(--color-black) a(10%)) | 未选中聚焦光环色 |
--radio-checked-focus-color | color-mod(var(--color-primary) a(26%)) | 选中聚焦光环色 |
--radio-text-color | var(--color-black) | 标签文字颜色 |
--radio-disabled-color | color-mod(var(--color-black) a(26%)) | 禁用态颜色 |
--radio-text-font-size | calc(1.4 * var(--unit)) | 标签文字字号 |
七、TypeScript 支持
组件附带了完整的类型声明:
- components/radio/index.d.ts:模块入口,默认导出
RadioButton,并重新导出RadioGroup及相关类型; - components/radio/RadioButton.d.ts:
RadioButtonProps(含checked、disabled、label、name、onBlur、onChange、onFocus、value、theme)与RadioButtonTheme; - components/radio/RadioGroup.d.ts:
RadioGroupProps(含children、disabled、name、onChange、value)。
在 TypeScript 项目中可以直接获得属性提示与类型校验;spec/ts/radio.tsx 提供了可参考的 TS 用法示例。
八、小结
react-toolbox 的 RadioButton 是一组"受控容器 + 独立选项"的经典组合:
- 使用上,牢记
RadioGroup的value/onChange是状态中枢,checked完全由组计算得出,按钮自身的checked属性实际上由父级传递; - 结构上,按钮由隐藏的原生 input、CSS 绘制的圆点、文本标签与 ripple 波纹叠加而成,天然具备键盘聚焦与无障碍支持;
- 主题上,通过 react-css-themr 注入
field、radio、radioChecked、ripple、disabled、text、input等类名即可深度定制,配合 components/radio/config.module.css 的变量可实现尺寸与配色的一体化调整。
对照 spec/components/radio.js 中的演示,你可以直接在其基础上验证受控切换、禁用态与焦点事件的全部行为。
- 前端
- UI组件
【免费下载链接】react-toolbox
A set of React components implementing Google's Material Design specification with the power of CSS Modules
相关推荐
Ant Design 按钮样式单选框:RadioButton 与 RadioGroup 组合实战指南
Ant Design 按钮样式单选框:RadioButton 与 RadioGroup 组合实战指南 Ant Design 在标准圆形单选控件之外,提供了按钮样
UI组件前端设计系统PyPagekite深度解析:Python实现的隧道反向代理工具
PyPagekite深度解析:Python实现的隧道反向代理工具 PyPagekite是一款基于Python实现的隧道反向代理工具,能够轻松穿透NAT和防火墙限
Material Components for Android 单选按钮(RadioButton)开发指南:M3 属性、状态与主题定制
Material Components for Android 单选按钮(RadioButton)开发指南:M3 属性、状态与主题定制 Material Com
UI组件移动开发设计系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考