无法使用name属性时如何实现自定义单选按钮组件的分组功能
单选按钮分组实现方案
单选分组的互斥逻辑应该在RadioGroup组件层级实现,这是最符合逻辑分层的通用方案:单个Radio只负责自身的样式和交互触发,同组的状态统一由上层RadioGroup管理,天然就实现了选中互斥的效果。
具体实现步骤
1. 实现RadioGroup组件,用Context共享组内状态
RadioGroup支持受控/非受控两种模式:
- 受控模式:用户传入
value和onChange,由用户外部管理选中状态 - 非受控模式:组件内部维护选中状态,支持传入
defaultValue设置初始值
代码示例:
import { createContext, useContext, useState, FC } from 'react'; // 定义RadioGroup的Context类型 type RadioGroupContextType = { currentValue: any; disabled: boolean; onChange: (value: any) => void; } const RadioGroupContext = createContext<RadioGroupContextType | null>(null); // RadioGroup组件props定义 type RadioGroupProps = { children: React.ReactNode; value?: any; defaultValue?: any; disabled?: boolean; onChange?: (value: any) => void; } export const RadioGroup: FC<RadioGroupProps> = ({ children, value, defaultValue, disabled = false, onChange }) => { // 非受控模式下内部维护状态 const [innerValue, setInnerValue] = useState(defaultValue); // 优先用外部传入的受控value const currentValue = value !== undefined ? value : innerValue; const handleChange = (newValue: any) => { // 非受控模式更新内部状态 if (value === undefined) { setInnerValue(newValue); } // 触发外部回调 onChange?.(newValue); } return ( <RadioGroupContext.Provider value={{ currentValue, disabled, onChange: handleChange }}> <div className="radio-group" style={{ display: 'flex', gap: '16px' }}> {children} </div> </RadioGroupContext.Provider> ) } // 封装Context获取hook,方便Radio调用 export const useRadioGroup = () => { return useContext(RadioGroupContext); }
2. 改造现有Radio组件,对接Group的Context
不需要再让用户手动给每个Radio传checked属性,自动从Context里取状态对比即可:
// 原有类型定义保持不变 type RadioProps = { checked?: boolean; children?: any; disabled?: boolean; label?: string; onClick?: (value?: any) => void; size?: RadioSize; value?: any; }; export const Radio: FC<RadioProps> = ({ disabled = false, onClick, label = "", value, size = "small", ...rest }) => { // 获取同组的共享状态 const groupContext = useRadioGroup(); // 如果在Group内,优先用Group的状态和配置 const checked = groupContext ? groupContext.currentValue === value : rest.checked; const isDisabled = groupContext ? (groupContext.disabled || disabled) : disabled; const handleClick = () => { if (isDisabled) return; // 触发Group的状态更新 groupContext?.onChange(value); // 保留原有自定义点击回调 onClick?.(value); } return ( // 注意:原代码中Radio内部嵌套RadioGroup是错误写法,替换为普通容器即可 <div onClick={handleClick} style={{ display: 'flex', alignItems: 'center', cursor: isDisabled ? 'not-allowed' : 'pointer' }}> <StyledRadio {...rest} disabled = {isDisabled} size = {size} checked = {checked} /> <RadioLabel size={size}>{label}</RadioLabel> </div> ); };
3. 实际使用示例
// 非受控模式,组件内部维护状态 <RadioGroup defaultValue="option1" onChange={(val) => console.log('选中了', val)}> <Radio label="选项1" value="option1" /> <Radio label="选项2" value="option2" /> <Radio label="选项3" value="option3" disabled /> </RadioGroup> // 受控模式,外部管理状态 const [selected, setSelected] = useState('option2'); <RadioGroup value={selected} onChange={setSelected}> <Radio label="选项1" value="option1" /> <Radio label="选项2" value="option2" /> </RadioGroup>
方案优势
- 逻辑分层清晰,单个Radio不需要关心组内其他组件的状态,所有互斥逻辑统一在Group层处理
- 支持嵌套分组,不同RadioGroup之间互不干扰
- 兼容受控/非受控两种使用模式,覆盖大部分业务场景
- 天然支持全局禁用,只需要给RadioGroup加disabled属性,组内所有Radio都会自动生效
内容的提问来源于stack exchange,提问作者agobago
相关产品推荐
相关产品推荐

