Radio.Group 的简单实现 (React context)

902 阅读1分钟

参考 antd 的 Radio.Group 的简单实现

  • 核心点: React context 链接
  • 表现形式一组互斥的 Radio 且可以自定义布局,如下图所示 image.png

组件使用方式

// 参考 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源码

  1. 入口文件

    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
    
  2. 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>
      )
    }
    
  3. 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>
      )
    }
    
  4. context

    import React from 'react'
    
    const RadioGroupContext = React.createContext<any>(null)
    export default RadioGroupContext