React实现checkbox group多组选项和标签组显示的联动

实现功能:勾选checkbox项,确定后,已勾选的checkbox项以tag标签的形式展示,tag标签可快捷删除。

checkbox group

tags

实现过程:

  • 使用React。
  • 使用Ant Design的Checkbox、Tag组件。
  • 整个组件主要分为两个部分:多选框组和Tag标签组。

1. 多选框组

class AddInfo extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            checkedList: [], // checkbox已选择的选项
            indeterminate: [], // 全选框-已有选择非全选
            checkAll: {}, // checkbox group 的全部title状态true/false
            tempList: [], // 临时存储checkbox已选择的选项
            checkTitle: {} // checkbox group中已选择的title(全选)
        };
    }

    /* 确定勾选 */
    handleOk = () => {
        if (this.state.tempList.length > 0) {
            // 将已选择信息传给Tags组件
            this.props.getChecked({
                checkedItem: this.state.tempList,
                checkAll: this.state.checkAll,
                indeterminate: this.state.indeterminate,
                checkTitle: this.state.checkTitle
            });
        }
    }

    /* checkbox单选 */
    onChange = (allCheckArr, checkedList) => {
        let checkAll = this.state.checkAll;
        let indeterminate = [];
        let checkTitle = {};
        Object.keys(allCheckArr).forEach((title) => {
            checkTitle[title] = 0;
            for (let checkedItem of checkedList || []) {
                if (allCheckArr[title].includes(checkedItem)) {
                    checkTitle[title]++;
                    checkAll[title] = checkTitle[title] === allCheckArr[title].length;
                    indeterminate[title] = !!checkTitle[title] && (checkTitle[title] < allCheckArr[title].length);
                }
            }
            if (checkTitle[title] === 0) { // 选项组下仅有一个选项时取消选中
                checkAll[title] = false;
            }
        });
        this.setState({
            checkedList,
            tempList:checkedList,
            indeterminate: indeterminate,
            checkAll: checkAll,
            checkTitle: checkTitle
        });
    }

    /* checkbox全选 */
    onCheckAllChange = (allCheckArr, title, e) => {
        this.state.checkAll[title] = e.target.checked;
        let checkedListT = [];
        checkedListT.push(...this.state.checkedList);
        let indeterminate = this.state.indeterminate || [];
        let checkTitle = this.state.checkTitle || {};
        if (e.target.checked === true) { // 全选
            checkedListT.push(...allCheckArr[title]);
            checkedListT = Array.from(new Set(checkedListT)); // 去重(原先indeterminate为true的情况)
            checkTitle[title] = allCheckArr[title].length;
        } else { // 取消全选
            let common = checkedListT.filter(v => allCheckArr[title].includes(v));
            checkedListT = checkedListT.concat(common).filter(v => checkedListT.includes(v) && !common.includes(v));
            checkTitle[title] = 0;
        }
        indeterminate[title] = false;
        this.setState({
            tempList: checkedListT,
            checkedList: checkedListT,
            indeterminate: indeterminate,
            checkTitle: checkTitle
        });
    }

    render() {
        const { checkedList, checkAll, indeterminate } = this.state;
        const { allCheckArr } = this.props;
        return (
            <div className={styles.modalcontent} >
                {
                    allCheckArr.map( ({ title, value }, key ) => (
                        <div className={styles.checksgroup}>
                            <div>
                                <Checkbox
                                indeterminate={indeterminate[title]}
                                onChange={this.onCheckAllChange.bind(this, allCheckArr, title)}
                                checked={checkAll[title]}
                                >
                                    {title}
                                </Checkbox>
                            </div>
                            <br />
                            <CheckboxGroup className={styles.contents} options={value} value={checkedList} onChange={this.onChange.bind(this, allCheckArr)} />
                        </div>
                ))}
            </div>
        );
    }
}

export default AddInfo;

  • 由于Ant Design官网上checkbox group的示例代码只有一个check group,本组件是可以有多组的情况,因此主要通过checkedList,checkAll,indeterminate,checkTitle几个状态控制checkbox group与单个的checkbox的全勾选、半勾选、无勾选几种情况的联动。
  • checkbox单选的操作是传入当前选择的所有的选项,然后与原先的可选项对比,计算出checkAll,indeterminate,checkTitle的值。每次要先将checkAll,indeterminate,checkTitle置空,遍历所有的已选项和待选项。
  • checkbox全选的函数本来是可以复用单选的操作,但是全选之后得出checkAll,indeterminate,checkTitle的值的过程比单选更简单一些,不用遍历选项数组,所以重写了全选的逻辑,没有复用单选的函数,虽然代码量多几行,但是执行过程更简单一些。

2. Tag标签组

import React from 'react';
import { Tag } from 'antd';
import styles from './index.less';

class Tags extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            items: this.props.items, // 需要显示的tag数组
            checkAll: this.props.checkAll, // 该tag所在的数组元素是否全部作为tag存在
            indeterminate: this.props.indeterminate, // 该tag所在的数组元素是否部分作为tag存在
            allCheckArr: this.props.allCheckArr, // 该tag所在的数组
            checkTitle: this.props.checkTitle // 该tag所在的数组元素作为tag存在的数量
        };
    }
    componentWillReceiveProps = ( value, nextState) => {
        this.setState({
            items: value.items,
            checkAll: value.checkAll,
            indeterminate: value.indeterminate,
            allCheckArr: value.allCheckArr,
            checkTitle: value.checkTitle
        });
    }

    delete = (key, value, e) => {
        e.preventDefault();
        let items = this.state.items;
        let checkAll = this.state.checkAll;
        let indeterminate = this.state.indeterminate;
        let allCheckArr = this.state.allCheckArr;
        let checkTitle = this.state.checkTitle;
        items.splice(key, 1);
        for (let title in allCheckArr) {
            for (let item of allCheckArr[title]) {
                if (item === value) {
                    checkTitle[title]--;
                    checkAll[title] = false;
                    if (checkTitle[title] === 0) { // 该选项组下的选项全部删除
                        indeterminate[title] = false;
                    } else {
                        indeterminate[title] = true;
                    }
                }
            }
        }
        this.setState({
            items: items,
            checkAll: checkAll,
            indeterminate: indeterminate,
            checkTitle: checkTitle
        });
        this.props.changeCheckeditems(items);
    }
    render() {
        const items = this.state.items?this.state.items:[];
        return (
            <div>
                {
                    items.map((value, key) => (
                        <Tag className={styles.singletag} closable key={key} onClose={this.delete.bind(this, key, value)}>{value}</Tag>
                    ))}
            </div>
        );
    }
}

export default Tags;

在多选框组对选项勾选之后,将选择结果传入Tags标签组件,该组件以Tag标签将已勾选的选项展示出来。Tag标签可以点击右边的“x”快捷删除,删除后,多选框组中相应的选项也会取消勾选。

3. 组件使用

这两个组件放在同一个父组件中使用,实现值传递。

class parent extends React.Component {
    /* 获取待选择选项 */
    getAllCheckArr = () => {
       ...
                    this.setState({
                        allCheckArr 
                        ...
                    });
         ...
    }
 
    /* 获取checkbox选择的选项 */
    getChecked = (val) => {
        this.setState({
            checkedItem: val.checkedItem,
            checkAll: val.checkAll,
            indeterminate: val.indeterminate,
            checkTitle: val.checkTitle
        });
    }
  
    /* 获取tags删除后的选项 */
    changeChecked = (val) => {
        ...
        this.setState({
            changedItem: val
        });
        ...
    }
    render() {
        const { checkedItem, changedItem,, checkAll, indeterminate, checkTitle } = this.state;
        return (
            ...
                <AddInfo
                       checkList={this.state.checkedItem}
                       allCheckArr={this.state.allCheckArr|| []}
                       getChecked={this.getChecked.bind(this)}
                 />
                 <Tags
                       allCheckArr={this.state.allCheckArr|| []}
                       checkAll={checkAll}
                       checkTitle={checkTitle}
                       indeterminate={indeterminate}
                       items={checkedItem}
                       changeChecked={this.changeChecked.bind(this)}
                 />
          ...
        );
    }
}

代码经过了比较大程度的删改,删除了许多无关功能,只保留了组件功能的核心部分,因此仅供参考实现思路。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容

  • 专业考题类型管理运行工作负责人一般作业考题内容选项A选项B选项C选项D选项E选项F正确答案 变电单选GYSZ本规程...
    小白兔去钓鱼阅读 8,970评论 0 13
  • 这个序可能会有些长 先作个自我介绍,我是一名交互设计师,90后。我并不怎么喜欢编辑文章或写点什么,就是因为懒,所以...
    IxDKN阅读 10,984评论 16 160
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生x阅读 15,967评论 3 119
  • 进入十一月,内心却复苏了一角。 你看路旁那一垛垛的枯草,你能想像一个月前它们的样子吗? 因为上课,几乎每天都抄近路...
    单人火锅阅读 230评论 0 0
  • 最近疯狂看意大利纪录片,其中一道比西科题吸引了我。传统的比西科题是用制作面包的发酵面团来做的,搭配香辣口味的番茄肉...
    越王的小馆阅读 198评论 0 0