1. 程式人生 > >React實現checkbox group多組選項和標籤組顯示的聯動

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)}
                 />
          ...
        );
    }
}

程式碼經過了比較大程度的刪改,刪除了許多無關功能,只保留了元件功能的核心部分,因此直接copy可能會有報錯。。。