React造轮子(reacthook实现一套自己的组件库)轮子公开课——第五课【Radio、RadioButton、RadioGroup】组件

一、组件库介绍

有兴趣的同学 可以先着手看源码,之前写的一系列开源组件库项目(有帮助的同学也帮忙点个star👍)

名称官网github
tinkerbell-ui(vue2.0)http://tinkerbell.tophttps://github.com/hanbingxu82/tinkerbell-ui
tinkerbell-ui(vue3.0)https://github.com/hanbingxu82/tinkerbell-ui-next
tinkerbell-ui(react-hook)https://github.com/hanbingxu82/tinkerbell-ui-react

二、Radio、RadioButton、RadioGroup组件流程介绍

在一个ui组件库当中其实,表单组件可以称之为重中之重,因为现在基本上pc端大部分开发还是面向的b端,所以基本上表单组件,在每个小模块当中是基本上都可以使用到的,那么关于 radio 组件我相信大家也都很熟悉,因为不免得每个表单中基本都带有着单选框组件,像如下,其实本身html标签的input的type给个radio属性也就是最初始的单选组件
在这里插入图片描述
那么我们现在肯定也要捋清楚一个逻辑就是我们怎么去实现这个radio单选框组件呢?其实有两种思路:


1、我们采用一个div去做底层,然后给这个div去设置样式,实现对应的点击事件,触发回显值,对应点击设置高亮等。但是这样也有弊端就是什么,我们外层绑定一个原始的form是无法提交表单值的、再有无法抓取value值、无法使用fieldset进行统一禁用这些,表单的原始功能就无法模拟了。

2、我们采用 input type=“radio” 去做一个底层,当然因为本身也是携带着radio样式的,这样我们只需要改样式即可,然后同样的本身也带有着点击事件默认行为等,我们直接使用默认的一系列方法即可


所以综上所述,我们的 radio 组件底层既采用 input type=“radio” 去实现功能,再有像RadioButton的话,我们也可以做一个简单的组件封装,底层我们还是一样的,就是设置边框以及选中高亮这些实现对应的功能,再有至于RadioGroup的话,总的来说它是用来包裹 Radio 、RadioButton组件的,我们可以直接在这个组件上面设置默认的选中值,以及回传值,以此来实现功能,所以接下来我们总结功能:

Radio:
type: string // type类型 判断颜色
checked: boolean // 是否选中
disabled: boolean // 禁用项
groupValue: any // groupValue 值是否与value值相等 逻辑上来说可传可不传 当时只是为了做判断用的 因为 value 也同样可以给与初始值
value: any // value初始值
label: string // 初始 lable 文本
name?: string // 原生 name 属性


RadioButton:
type: string // type类型 判断颜色
checked: boolean // 选中状态
disabled: boolean // 禁用状态
groupValue: any // groupValue 值是否与value值相等 逻辑上来说可传可不传 当时只是为了做判断用的 因为 value 也同样可以给与初始值
label:string, // 初始 lable 文本
value: any // value初始值
name?: string // 原生 name 属性
size: string // 按钮大小
buttonStyle: string // style样式


RadioGroup:
name?: string // 统一原生name值
buttonStyle: string // 如果是button类型 统一的buttonStyle
disabled: boolean // 统一禁用
options: any // js编程式组件
optionType: string // js编程式类型
size: string // 大小
value: any // value值

在这里也是讲解一下 RadioGroup 的options,js编程式的组件,在这里其实也是参考了antd传入options,然后实现组件功能,随机我们也在我们的组件库当中去实现此功能。
在这里插入图片描述

三、代码详解

3.1、Radio.tsx

import React from 'react'
import './index.scss'

const classnames = require('classnames')
interface Iprops {
  type: string // type类型 判断颜色
  checked: boolean // 是否选中
  disabled: boolean // 禁用项
  groupValue: any // groupValue 值是否与value值相等 逻辑上来说可传可不传 当时只是为了做判断用的  因为 value 也同样可以给与初始值
  value: any // value初始值
  label: string // 初始 lable 文本
  name?: string // 原生 name 属性
}
function Radio(props: any) {
  const {
    type = 'default',
    disabled = false,
    groupValue,
    checked = false,
    value,
    label,
    name
  }: Iprops = props
  function handleChange(evt: any) {
    props.onChange && props.onChange(evt.target.value)
  }
  return (
    <div
      className={[
        'tb-radio',
        classnames({
          'is-disabled': disabled
        })
      ].join(' ')}
    >
      <label>
        <input
          type='radio'
          checked={groupValue ? (groupValue == value ? true : false) : checked}
          name={name}
          disabled={disabled}
          onChange={handleChange}
          value={value}
          className={[
            `radio-type_${type}`,
            classnames({
              'is-disabled': disabled
            })
          ].join(' ')}
        />
        <span>{props.children ? props.children : label ? label : value}</span>
      </label>
    </div>
  )
}
export default Radio

3.2、Radio / index.scss

@import '../../style/variables.scss';


$type:('success', 'primary', 'danger', 'warning', 'info', 'default');

@function typeFunction($t) {
  @if $t==success {
    @return $color-success
  }

  @else if $t==primary {
    @return $color-primary
  }

  @else if $t==info {
    @return $color-info
  }

  @else if $t==danger {
    @return $color-danger
  }

  @else if $t==warning {
    @return $color-warning
  }

  @return $color-primary
}

@function typeDisabledFunction($t) {
  @if $t==success {
    @return $color-success-light2
  }

  @else if $t==primary {
    @return $color-primary-light2
  }

  @else if $t==info {
    @return $color-info-light2
  }

  @else if $t==danger {
    @return $color-danger-light2
  }

  @else if $t==warning {
    @return $color-warning-light2
  }

  @return $color-primary-light2
}

.tb-radio {
  display: inline-block;

  label {
    display: flex;
    align-items: center;
  }
}

@each $t in $type {
  .radio-type_#{$t} {
    width: 20px;
    height: 20px;
    appearance: none;
    position: relative;
    outline: none;
    vertical-align: bottom;
    cursor: pointer;
  }

  .radio-type_#{$t}:before {
    content: "";
    width: 75%;
    height: 75%;
    border: 1px solid typeFunction($t);
    display: inline-block;
    border-radius: 50%;
    vertical-align: middle;
    cursor: pointer;
  }

  .radio-type_#{$t}.is-disabled:before {
    border-color: typeDisabledFunction($t);
    cursor: not-allowed;
  }

  .radio-type_#{$t}:checked:before {
    content: "";
    width: 75%;
    height: 75%;
    border: 1px solid typeFunction($t);
    display: inline-block;
    border-radius: 50%;
    vertical-align: middle;
    cursor: pointer;
  }

  .radio-type_#{$t}.is-disabled:checked:before {
    border-color: typeDisabledFunction($t);
    cursor: not-allowed;
  }

  .radio-type_#{$t}:checked:after {
    content: "";
    width: 45%;
    height: 45%;
    text-align: center;
    background: typeFunction($t);
    border-radius: 50%;
    display: block;
    position: absolute;
    top: 20%;
    left: 20%;
    cursor: pointer;
  }

  .radio-type_#{$t}.is-disabled:checked:after {
    background: typeDisabledFunction($t);
    cursor: not-allowed;
  }

  .radio-type_#{$t}+span {
    cursor: pointer;
  }

  .radio-type_#{$t}:checked+span {
    color: typeFunction($t);
    cursor: pointer;
  }

  .radio-type_#{$t}.is-disabled+span {
    color: $color-text-disabled;
    cursor: not-allowed;
  }

  .radio-type_#{$t}.is-disabled:checked+span {
    color: typeDisabledFunction($t);
    cursor: not-allowed;
  }
}

3.3、RadioButton.tsx

import React from 'react'
import './index.scss'

const classnames = require('classnames')
interface Iprops {
  type: string   // type类型 判断颜色
  checked: boolean  // 选中状态
  disabled: boolean  // 禁用状态
  groupValue: any // groupValue 值是否与value值相等 逻辑上来说可传可不传 当时只是为了做判断用的  因为 value 也同样可以给与初始值
  label:string,  // 初始 lable 文本
  value: any // value初始值
  name?: string // 原生 name 属性
  size: string // 按钮大小
  buttonStyle: string // style样式
}
function RadioButton(props: any) {
  const {
    type = 'default',
    disabled = false,
    groupValue,
    checked,
    value,
    name,
    label,
    size = 'default',
    buttonStyle = 'outline'
  }: Iprops = props
  function handleChange(evt: any) {
    props.onChange && props.onChange(evt)
  }
  return (
    <div
      className={[
        'tb-radio-button',
        classnames({
          'is-disabled': disabled
        })
      ].join(' ')}
    >
      <label>
        <input
          type='radio'
          checked={groupValue ? (groupValue == value ? true : false) : checked}
          name={name}
          disabled={disabled}
          onChange={handleChange}
          value={value}
          className={[
            `radio-button-type_${type}`,
            classnames({
              'is-disabled': disabled
            })
          ].join(' ')}
        />
        <span
          className={[
            `tb-radio-button--${size}`,
            `tb-radio-button--${type}_${buttonStyle}`
          ].join(' ')}
        >
          {props.children ? props.children : label ? label : value}
        </span>
      </label>
    </div>
  )
}
export default RadioButton

3.4、RadioButton / index.scss

@import '../../style/variables.scss';


$type:('success', 'primary', 'danger', 'warning', 'info', 'default');

@function typeFunction($t) {
  @if $t==success {
    @return $color-success
  }

  @else if $t==primary {
    @return $color-primary
  }

  @else if $t==info {
    @return $color-info
  }

  @else if $t==danger {
    @return $color-danger
  }

  @else if $t==warning {
    @return $color-warning
  }

  @return $color-primary
}

@function typeDisabledFunction($t) {
  @if $t==success {
    @return $color-success-light2
  }

  @else if $t==primary {
    @return $color-primary-light2
  }

  @else if $t==info {
    @return $color-info-light2
  }

  @else if $t==danger {
    @return $color-danger-light2
  }

  @else if $t==warning {
    @return $color-warning-light2
  }

  @return $color-primary-light2
}

.tb-radio-button {
  display: inline-block;

}

@each $t in $type {
  .radio-button-type_#{$t} {
    display: none;
  }

  .radio-button-type_#{$t}+span {
    position: relative;
    display: inline-block;
    vertical-align: middle;
    height: $default-height;
    line-height: $default-height - 2;
    white-space: nowrap;
    cursor: pointer;
    background: $color-white;
    border: $border-base;
    color: $color-text-default;
    -webkit-appearance: none;
    text-align: center;
    box-sizing: border-box;
    outline: none;
    margin: 0;
    transition: $animation-duration-base;
    user-select: none;
    padding: 0 15px;
    font-size: $base-font-size;
    border-radius: $border-base-radius;
  }

  .radio-button-type_#{$t}+.tb-radio-button--mini {
    height: $mini-height;
    line-height: $mini-height - 2;
    padding: 0 7px;
  }

  .radio-button-type_#{$t}+.tb-radio-button--small {
    height: $small-height;
    line-height: $small-height - 2;
    padding: 0 7px;
  }

  .radio-button-type_#{$t}+.tb-radio-button--large {
    height: $large-height;
    line-height: $large-height - 2;
    padding: 0 14px;
    font-size: $header-font-size;
  }

  .radio-button-type_#{$t}:checked+span {
    border-color: typeFunction($t);
    color: typeFunction($t);
    cursor: pointer;
  }

  .radio-button-type_#{$t}.is-disabled+span {
    color: $color-text-disabled;
    cursor: not-allowed;
  }

  .radio-button-type_#{$t}.is-disabled:checked+span {
    color: typeDisabledFunction($t);
    cursor: not-allowed;
  }


  .radio-button-type_#{$t}:checked+.tb-radio-button--#{$t}_solid {
    background-color: typeFunction($t);
    color: $color-white;
    cursor: pointer;
  }

  .radio-button-type_#{$t}.is-disabled+.tb-radio-button--#{$t}_solid {
    color: $color-text-disabled;
    background-color: $btn-disable-color;
    cursor: not-allowed;
  }

  .radio-button-type_#{$t}.is-disabled:checked+.tb-radio-button--#{$t}_solid {
    color: $color-white;
    background-color: typeDisabledFunction($t);
    border-color: typeDisabledFunction($t);
    cursor: not-allowed;
  }
}

3.5、RadioGroup.tsx


import React from 'react'
import Radio from '../Radio/index'
import RadioButton from '../RadioButton/index'
interface Iprops {
  name?: string // 统一原生name值
  buttonStyle: string // 如果是button类型 统一的buttonStyle
  disabled: boolean // 统一禁用
  options: any // js编程式组件
  optionType: string // js编程式类型
  size: string // 大小
  value: any // value值
}
function RadioGroup(props: any) {
  const {
    name,
    value,
    options = [],
    buttonStyle,
    size,
    optionType = 'default'
  }: Iprops = props

  function handleChange(val: any) {
    props.onChange && props.onChange(val)
  }
  //  options 循环遍历
  const radioDom = options.map((item: any) => {
    if (optionType === 'button') {
      return (
        <RadioButton
          key={item.value}
          value={item.value||''}
          groupValue={value}
          name={name}
          size={size}
          buttonStyle={buttonStyle}
          disabled={item.disabled ? item.disabled : null}
          onChange={handleChange}
        >
          {item.label}
        </RadioButton>
      )
    } else {
      return (
        <Radio
          key={item.value}
          value={item.value}
          groupValue={value}
          name={name}
          disabled={item.disabled ? item.disabled : null}
          onChange={handleChange}
        >
          {item.label}
        </Radio>
      )
    }
  })
  //  遍历dom子节点方式
  const radioItems = React.Children.map(props.children, (item) => {
    return React.cloneElement(item, {
      item,
      componentName: 'radioGroup',
      groupValue: value,
      name,
      onChange: props.onChange,
      buttonStyle,
      size
    })
  })

  return (
    <div className='tb-radio-group'>{radioItems ? radioItems : radioDom}</div>
  )
}
export default RadioGroup

3.6、variables.scss

// $font-path : './fonts'

/* Color
-------------------------- */
$color-white : #FFFFFF;
$color-white-light : rgba(255, 255, 255, 0.65);
$color-white-light2 : rgba(255, 255, 255, 0.35);

$color-primary : #1089ff;
$color-primary-light1 : mix(#FFFFFF, $color-primary, 20%);
$color-primary-light2 : mix(#FFFFFF, $color-primary, 40%);
$color-primary-light3 : mix(#FFFFFF, $color-primary, 60%);
$color-primary-light4 : mix(#FFFFFF, $color-primary, 80%);
$color-primary-light5 : mix(#FFFFFF, $color-primary, 90%);
$color-primary-light6 : mix(#FFFFFF, $color-primary, 95%);
$color-primary-active : mix(#000000, $color-primary, 10%);

$color-success :#52c41a;
$color-success-light1 : mix(#FFFFFF, $color-success, 20%);
$color-success-light2 : mix(#FFFFFF, $color-success, 40%);
$color-success-light3 : mix(#FFFFFF, $color-success, 60%);
$color-success-light4 : mix(#FFFFFF, $color-success, 80%);
$color-success-light5 : mix(#FFFFFF, $color-success, 90%);
$color-success-light6 : mix(#FFFFFF, $color-success, 95%);
$color-success-active : mix(#000000, $color-success, 10%);

$color-info : #35495E;
$color-info-light1 : mix(#FFFFFF, $color-info, 20%);
$color-info-light2 : mix(#FFFFFF, $color-info, 40%);
$color-info-light3 : mix(#FFFFFF, $color-info, 60%);
$color-info-light4 : mix(#FFFFFF, $color-info, 80%);
$color-info-light5 : mix(#FFFFFF, $color-info, 90%);
$color-info-light6 : mix(#FFFFFF, $color-info, 95%);
$color-info-active : mix(#000000, $color-info, 10%);

$color-warning : #fea638;
$color-warning-light1 : mix(#FFFFFF, $color-warning, 20%);
$color-warning-light2 : mix(#FFFFFF, $color-warning, 40%);
$color-warning-light3 : mix(#FFFFFF, $color-warning, 60%);
$color-warning-light4 : mix(#FFFFFF, $color-warning, 80%);
$color-warning-light5 : mix(#FFFFFF, $color-warning, 90%);
$color-warning-light6 : mix(#FFFFFF, $color-warning, 95%);
$color-warning-active : mix(#000000, $color-warning, 10%);

$color-danger : #ff4d4f;
$color-danger-light1 : mix(#FFFFFF, $color-danger, 20%);
$color-danger-light2 : mix(#FFFFFF, $color-danger, 40%);
$color-danger-light3 : mix(#FFFFFF, $color-danger, 60%);
$color-danger-light4 : mix(#FFFFFF, $color-danger, 80%);
$color-danger-light5 : mix(#FFFFFF, $color-danger, 90%);
$color-danger-light6 : mix(#FFFFFF, $color-danger, 95%);
$color-danger-active : mix(#000000, $color-danger, 10%);

/* text-color
-------------------------- */
$color-text-primary : rgba(0, 0, 0, .85);
$color-text-default : rgba(0, 0, 0, .65);
$color-text-secondary : rgba(0, 0, 0, .45);
$color-text-disabled : rgba(0, 0, 0, .25);
$color-text-placeholder : #C0C4CC;
$btn-disable-color : #c5c8ce;
$color-disabled-bg:rgb(245, 245, 245);
$color-disabled-border:rgb(217, 217, 217);

/* bg-color
-------------------------- */
$color-bg-fa : #fafafa;
$color-select-hover : #f5f5f5;
$color-effect-shadow : alpha($color-primary);
$color-input-shadow : alpha($color-primary);
$color-input-error-shadow : alpha($color-danger);

/* border
-------------------------- */
$border-color-base : #d9d9d9;
$border-color-light : #f0f0f0;
$border-base : 1px solid $border-color-base;
$border-width:1px;
$border-base-light : 1px solid $border-color-light;
$border-table : 1px solid #e8eaec;
$border-table-color : #e8eaec;

/* radius font-size
-------------------------- */
$border-base-radius : 2px;
$base-font-size : 14px;
$header-font-size : 16px;

/* height
-------------------------- */
$large-height : 36px;
$default-height : 32px;
$small-height : 28px;
$mini-height : 24px;
$base-line-height : 1.5715;

$animation-duration-slow : 0.3s;
$animation-duration-base : 0.2s;
$animation-duration-fast : 0.1s;

// placeholder
$placeholder-color : #c0c4cc;

四、效果呈现

在这里插入图片描述
在这里插入图片描述
至此效果实现

react 官网持续开发中,也请大家持续关注博主

结语

✨ 每天创作一点点
✨ 开心快乐一整天
✨ 点赞关注加收藏
✨ 美好一天又一天

铁铁们 感谢支持 我需要你们的三连 👍👍👍
请添加图片描述

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 15
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 15
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

归来巨星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值