在React项目中实现仿饿了么Checkbox多选按钮样式的效果组件

1,685 阅读5分钟

「这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战

前言

这是2022年的第二次更文,以前我不喜欢记录工作中遇到的一些问题,总觉得自己的脑容量可以记住所有,可事与愿违,最终还是逃不过一看就会一做就废的结果。现在觉得很有必要将工作中遇到的问题和思考加以总结,并输出成文,给今后的工作留下记录避免再次踩坑。

好了,今天要说的是:如何封装一个可多选的按钮组件

万物皆有源,需求不可能凭空产生,肯定是有人遇到或者使用过的这样一个类似的组件,觉得可以适用于我们业务需求中,所以不可置否的他带走需求走来了(没有一个需求是简单的)。

需求背景

由于我们在工作中一开始使用的是Vue技术栈进行开发,对应的UI框架自然选择的是Element UI框架,所以在后来将技术栈改为React时,选择的UI框架Antd中有部分组件效果没有提供。比如:ElementCheckbox多选框组件提供了按钮样式的多选效果组件,但是Antd没有提供,那怎么办咯,现成的没有那就只有自己开发一个组件并提供给组内的小伙伴调用。

微信截图_20220117165621.png

开发

搞就搞嘛,弄就完了!先来看UI设计图,知己知彼方能游刃有余:

微信截图_20220117171128.png

思考:它是作为查询条件,那我们要考虑编写的组件要怎么才能放在Form表单里面使用?那就是让组件成为受控组件,这样就能契合的配合Form一起使用,减少其他开发时间。

受控组件:在React中,可变状态通常保存在组件的状态属性中,并且只能使用 setState() 更新,而呈现表单的React组件也控制着在后续用户输入时该表单中发生的情况,这种由React控制的输入表单元素而改变其值的方式,称为受控组件。

非受控组件:表单数据由DOM本身处理。即不受setState()的控制,与传统的HTML表单输入相似,input输入值即显示最新值(使用 ref从DOM获取表单值)。

既然已经想得足够清楚,那应该没有啥问题,赶紧开搞开搞!

创建ReactCheckBox.tsx文件:

import React from 'react';
import './style.less'


// 默认专业
const List: any[] = [
    {id: 1, value: '建筑', label: '建'},
    {id: 2, value: '结构', label: '结'},
    {id: 3, value: '给排水', label: '水'},
    {id: 4, value: '电气', label: '电'},
    {id: 5, value: '暖通', label: '暖'}
]


interface Data {
    value: string | number;
    label: string;
    isActive?: boolean;
}


interface ReactCheckBoxProps {
    data?: Data[];            // 父组件传入的数据源,若无则使用majorList数据
    value?: any[];            // 选中的值
    size?: string;            // 尺寸大小
    onChange?: (checkList: any[], checked: boolean) => void; // change函数
}


/**
 * React多选组件
 * props
 * @constructor
 */
const ReactCheckBox = (props: ReactCheckBoxProps) => {
    const {data = List, value = [], size = 'default', onChange = (checkList: any[]) => {}} = props;

    /**
     * 选中事件
     * @param value
     * @param checked
     */
    const checkItem = (val: string, checked: boolean) => {
        let newCheckedList:any[] = [...value];
        newCheckedList = checked ? [...newCheckedList, val] : newCheckedList.filter(v => v != val);
        onChange(newCheckedList, true);
    }


    return (
        <div className={["bit-group-wrap", size].join(' ')}>
            {data.map((item: any, index: number) =>
                <div
                    className={
                        [
                            "bit-group-item",
                            value.includes(item.value) ? 'is-checked' : '',
                            item.isActive ? "is-actived" : ''
                        ].join(' ')
                    }
                    onClick={() => checkItem(item.value, !value.includes(item.value))}
                    key={index}
                >
                    {item.label}
                </div>
            )}
        </div>
    )
}


export default ReactCheckBox;

创建style.less样式文件:

.bit-group-wrap {
  display: flex;
  flex-direction: row;
  justify-content: flex-start;
  align-items: center;
  height: 30px;

  .bit-group-item {
    line-height: 1;
    font-weight: 500;
    white-space: nowrap;
    vertical-align: middle;
    cursor: pointer;
    background: #fff;
    border: 1px solid #dcdfe6;
    border-left: 0;
    color: #606266;
    -webkit-appearance: none;
    text-align: center;
    box-sizing: border-box;
    outline: none;
    margin: 0;
    transition: all .3s cubic-bezier(.645, .045, .355, 1);
    -moz-user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
    padding: 8px 20px;
    font-size: 14px;
    border-radius: 0;
  }

  .bit-group-item:first-child {
    border-left: 1px solid #dcdfe6;
  }

  .is-checked {
    color: #fff;
    background-color: #409eff;
    border-color: #409eff;
    box-shadow: -1px 0 0 0 #8cc5ff;
  }

  .is-checked:first-child {
    border-left-color: #409eff;
  }

  .is-actived {
    color: #5D5D5D;
    background-color: #E3E6EE;
    border-color: rgba(0, 0, 0, .15);
    box-shadow: -1px 0 0 0 rgba(0, 0, 0, .15);
  }
}

.small {
    height: 24px;
}
.default {
    height: 30px;
}

.large {
    height: 40px;
}

至此,组件的相关代码也已编写完毕,可以看出代码结构和逻辑不是很复杂,就是多选功能加复选样式的结合。代码中增加了valueonChange的属性,这是为了让组件支持在Form表单里面去使用,使之成为一个受控组件

使用方法:

1)导入组件:
import ReactCheckBox from "../../../component/ReactCkeckBox/ReactCheckBox";

2)使用组件:
const List: any[] = [
    {value: 1, label: '一般'},
    {value: 2, label: '重要'},
    {value: 3, label: '重大'}
]


<Form form={form}>
    <Form.Item label="专业:" name="majors" initialValue={['建筑', '结构', '电气', '暖通']}>
        <ReactCheckBox />
    </Form.Item>
    
    <Form.Item label="任务等级:" name="levels" initialValue={[1, 2]}>
        <ReactCheckBox data = {List} />
    </Form.Item>
</Form>

const monthList: any[] = [
    {value: '2021-09-17 00:00:00', label: '9月', isActive: false},
    {value: '2021-10-17 00:00:00', label: '10月', isActive: false},
    {value: '2021-11-17 00:00:00', label: '11月', isActive: false},
    {value: '2021-12-17 00:00:00', label: '12月', isActive: false},
    {value: '2022-01-17 00:00:00', label: '1月', isActive: true},
    {value: '2022-02-17 00:00:00', label: '2月', isActive: true},
    {value: '2022-03-17 00:00:00', label: '3月', isActive: false},
    {value: '2022-04-17 00:00:00', label: '4月', isActive: false},
    {value: '2022-05-17 00:00:00', label: '5月', isActive: false},
]

<Form form={tableForm}>
    <Form.Item label="客户任务时间分布:" name="filterMonth">
        <ReactCheckBox
            onChange={() => {
                // 统计埋点
                report.emit({actionId: 'xxxxx', action: ''})
            }}
            data={monthList}/>
    </Form.Item>
<Form form={form}>

实现效果:

微信截图_20220117180942.png

微信截图_20220117182252.png

API:

参数说明类型
data数据源any[]
size尺寸大小small/default/large
value指定选中的选项string[]
onChange变化时回调函数function(checkList: any[], checked: boolean)

万事开头难,其实需求在没有现有组件支持的情况下,组内的小伙伴就会主观的去认为有一定难度,但只要经过深思熟虑之后就会柳暗花明。仿饿了么多选按钮样式组件还可以继续优化迭代,在NPM上已经有相应的包供别人使用,但是还不够足够复用,后续会进行迭代升级更新版本,让其拥有更好地适用性。

往期精彩文章

后语

伙伴们,如果觉得本文对你有些许帮助,点个👍或者➕个关注在走呗^_^ 。另外如果本文章有问题或有不理解的部分,欢迎大家在评论区评论指出,我们一起讨论共勉。