【Day03】数据输入元件 - Radio

元件介绍

Radio 是一个单选框元件。让我们在一组选项当中选择其中一个选项。当我们的情境是希望用户可以一次看到所有选项时,可以使用 Radio Button。在 MUI 及 Antd 都有个共同的说明,就是 Radio Button 的选项不宜多,如果你的选项多到需要被折叠,那建议你使用更不占空间的下拉选单元件

参考设计 & 属性分析

checked 属性

checked 属性是每个 Radio Button 必备的属性,是一个 boolean 值,透过这个属性来决定该选项是否被选取。

状态属性

disabled 属性也是每个 Radio Button 的必备属性,表示这个 Radio 被禁用,禁止改变他的当前状态。

当然 Radio Button 应该就不会有 loading 属性了,毕竟 Radio Button 不是在当前改变的时候触发动作,是需要发送按钮按下之後才会将结果送出。

外观属性

大小的部分 MUI 以及 Antd 的一样是用 size 来控制,其中 MUI 的传入值可以是 medium, samll,而 Antd 的传入值可以是 large, middle, small

颜色的部分,MUI 提供 color 这个 props 让我们传入,传入值可以是 primary, secondary, default,而 Antd 从文件来看没有特别提供明显的介面让我们改变 Radio 的颜色,如果要改颜色可能需要再看要用什麽方法去覆写。

label 属性
Antd 的 label 属性是放在 Radio 的 children component 里面,而 MUI 的 label 及 labelPlacement 一样是由另外的元件 FormControlLabel 来独立控制。

Radio Group
由於 Radio Button 是单选选项,当介面有多个选项时,为了让同一个 Group 的 option 做到单选的效果,MUI 及 Antd 都提供了 Radio Group 的 wrapper component 来协助我们将这些选项编辑为同一个群组,介面的设计上看起来他们也是蛮有共识的。

// Antd Radio Group
<Radio.Group value={value} onChange={handleChangeAntdRadio}>
    <Radio value={1}>A</Radio>
    <Radio value={2}>B</Radio>
    <Radio value={3}>C</Radio>
    <Radio value={4}>D</Radio>
</Radio.Group>
// MUI Radio Group
<RadioGroup value={value} onChange={handleChangeMuiRadio} aria-label="gender" name="gender1" >
    <FormControlLabel value="female" control={<Radio />} label="Female" />
    <FormControlLabel value="male" control={<Radio />} label="Male" />
    <FormControlLabel value="other" control={<Radio />} label="Other" />
    <FormControlLabel value="disabled" disabled control={<Radio />} label="(Disabled option)" />
</RadioGroup>

为了做群组单选的操做,这边就不在单一个 Radio 上面监听 onChange 的事件,而是将 onChange 事件拉到外层的 wrapper 上面。并且也让我们在外层的 Radio Group 元件上能够传入 value 这个 props ,用来告诉群组中的选项哪一个是被选中的,若选项中的 value 跟传入 Radio Group 的 value 一致,那这个选项就是 checked = true 的状态。

介面设计

Radio Button

属性 说明 类型 默认值
value 用来帮助判断是否被选中 any
isChecked 开启或关闭 boolean false
isDisabled 禁用状态 boolean false
themeColor 设置颜色 primary, secondary, 色票 pirmary
children 内容, label element, string
onClick 点击事件 function(event: object) => void

Radio Group

属性 说明 类型 默认值
value 用来帮助判断下面 Radio 是否被选中 any
onChange 状态改变的 callback function function(event: object) => void
columns 用来决定 children 排版的栏位数 number 1

元件实作

Radio

Radio 的结构是相对於单纯的结构,主要分成两部分,第一部分是 Radio icon,第二部分是 label,我们的 label 是透过 children 来传入;而透过 isChecked 这个 props 来决定显示被选取或不被选取的样式。:

<StyledRadio
  onClick={isDisabled ? null : onClick}
  $isDisabled={isDisabled}
  $btnColor={btnColor}
  {...props}
>
  {
    isChecked
      ? <RadioButtonCheckedIcon className="radio__checked-icon" />
      : <RadioButtonUncheckedIcon className="radio__unchecked-icon" />
  }
  {!!children && <span>{children}</span>}
</StyledRadio>

在设计这个元件的时候,我将 checkdeIcon 以及 unCheckedIcon 各别给他一个 className,并且包覆在 StyledRadio 这个 wrapper 之下,主要的目的是我希望样式的变化能够透过 StyledRadio 这个父层的 styled-components 来处理就好,所以也就只需要把 props 传入 StyledRadio 就能够透过 className 来改变子层的样式,而不用每个元件都需要传入各别传入同样的 props。

const StyledRadio = styled.div`
  // {...其他样式省略}

  .radio__checked-icon {
    color: ${(props) => props.$btnColor};
  }

  .radio__unchecked-icon {
    color: ${(props) => (props.$isDisabled ? DISABLED_COLOR : '#808080')};
  }

  &:hover {
    .radio__unchecked-icon {
      color: ${(props) => (props.$isDisabled ? DISABLED_COLOR : props.$btnColor)};
    }
  }
`;

Radio Group

接着我们来做一个阳春的 Radio Group,主要的目的是希望能够透过它来统一管理子层单选的 Radio buttons,并且能够做一些简单的排版。

我希望使用起来可以像下面这样,只需要父层传入 value 及 onChange,子层的 Radio 传入 value,就能够做到单选效果:

<RadioGroup
  value={selectedValue}
  onChange={handleOnChange}
  columns={2}
  style={{ maxWidth: 500 }}
  {...args}
>
    <Radio value="male">Male</Radio>
    <Radio value="female">Female</Radio>
    <Radio value="others">Others</Radio>
</RadioGroup>

主要的作法如下,关键是会用到 React.Children.mapReact.cloneElement 这两个方法:

<StyledRadioGroup
  {...props}
>
  {React.Children.map(children, (child) => (
    React.cloneElement(child, {
      onClick: () => handleOnClick(child.props.value),
      isChecked: child.props.value === value,
    })
  ))}
</StyledRadioGroup>

React.Children.map 可以帮助我们将 array of Radio 的 children 做迭代,如同我们用 Array.prototype.map() 在处理一个阵列一样。

在迭代当中的每一个回圈,我们可以拿到的 child 就是一个 Radio element,此时再搭配 React.cloneElement 这个方法,藉此产生一个拥有原始 element 的 props 以及我们在这里新注入 props 的全新 element。

简单来说,就是我们想要把从 RadioGroup 传进来的 props 经过运算之後,当作新的 props 注入每一个 child element 里面,以这里为例就是我们把 RadioGroup 传入的 value 跟 child value 做比较,若相符就是被选中的 Radio,把这个 boolean 注入 isChecked,如此就能够用这样的方法达到 Radio 的单选功能。


Radio 元件原始码:
Source code

RadioGroup 元件原始码:
Source code

Storybook:
Radio


<<:  [Day12] Trait 与 STD 库中的 fs

>>:  Day-4 CLA以及bit乘法

[铁人赛 Day14] 来读 Hooks FAQ 文件-lifecycle methods 如何对照到 Hooks?

lifecycle methods 如何对照到 Hooks? constructor:Functio...

day17 : kafka服务应用 on K8S (上)

kafka是一套与昨天的NATS类似的分布式MQ系统,会用这两套也不是想要做差异比较,单纯只是有多一...

使用 OpenTelemetry api 自订义内容

Resources 在 OpenTelemetry 中,服务由资源描述,资源是在应用程序启动期间初始...

Day 25 - HBuilder X 产生 apk

Day 25 - HBuilder X 产生 apk 使用 HBuilder X 来开发手机端应用,...

[Day9]SQL函数:单列函数

SQL函数分为两种类型:单列函数及多列函数。 一个函数可以输入多个引数,引数可包含栏位名称、表示式、...