参考 antd 的 Radio.Group 的简单实现
- 核心点: React
context链接 - 表现形式一组互斥的 Radio 且可以自定义布局,如下图所示
组件使用方式
// 参考 antd https://ant.design/components/radio-cn/#RadioGroup
<Radio.Group value={value} onChange={value => setValue(value)}>
{[{}, {}, {}].map((check, index) => {
return (
<div className='mr-3' key={index}>
<Radio value={`${index}-xxx`}>{`Radio——${index}`}</Radio>
</div>
)
})}
</Radio.Group>
组件具体实现 antd源码
-
入口文件
import RadioComps, { TRadioProp } from './Radio' import Group from './Group' interface CompoundedComponent extends React.ForwardRefExoticComponent< TRadioProp & React.RefAttributes<HTMLElement> > { Group: typeof Group } const Radio = RadioComps as CompoundedComponent /** * Radio 组件出现一般是以一组的形式出现 */ Radio.Group = Group export default Radio -
Group.tsx
import { ReactElement } from 'react' import RadioGroupContext from './context' export type TRadioGroupProp = { children?: ReactElement | ReactElement[] value?: string | number onChange?: (value: any) => void } export default function Group({ children, value, onChange, }: TRadioGroupProp): JSX.Element { const onRadioChange = (e: TInputEvent) => { onChange?.(e.target.value) } return ( <RadioGroupContext.Provider value={{ value, onChange: onRadioChange }}> {children} </RadioGroupContext.Provider> ) } -
Radio 具体实现
import { ReactElement, useId, useContext } from 'react' import cn from 'classnames' import RadioGroupContext from './context' export type TRadioProp = { className?: string children?: string | ReactElement checked?: boolean value?: string | number disabled?: boolean onChange?: (e: TInputEvent) => void } export default function Radio({ className = '', children = '', disabled, checked, value, onChange, }: TRadioProp): JSX.Element { const groupContext = useContext(RadioGroupContext) const labelId = useId() const handleChange = (e: TInputEvent) => { onChange?.(e) groupContext?.onChange?.(e) } // 重点:当 groupContext 有值即 radio 是以 group 形式出现的,此时忽略 checked const isChecked = groupContext ? groupContext.value === value : checked return ( <div className={cn('flex items-center', className)}> <span className='relative flex'> <input className={cn( 'cursor-pointer w-6 h-6 rounded-full appearance-none', isChecked ? 'bg-blue' : 'border-2 border-dark-100 bg-transparent', )} id={labelId} type='radio' checked={isChecked} value={value} disabled={disabled} onChange={handleChange} /> {isChecked && ( <span className='bg-white rounded-full absolute w-9px h-9px left-1/2 top-1/2 translate-x-50 translate-y-50 pointer-events-none' /> )} </span> {children && ( <label className='pl-1 cursor-pointer' htmlFor={labelId}> {children} </label> )} </div> ) } -
context
import React from 'react' const RadioGroupContext = React.createContext<any>(null) export default RadioGroupContext