模仿 antd@4 table 自定義篩選表頭功能 => 做一個動態(tài)列 table

前言: 如果你經(jīng)常寫東西你就會發(fā)現(xiàn)宾添,寫的東西涉及原理和新知識增热、等需要主動學習的,閱讀量特別的低崎淳,閱讀量最高的基本是框架如何使用和使用中遇到哪些坑這種文章。我是不太喜歡去很深入的研究框架在項目中的用法愕把,但是沒辦法平時寫項目就的用到拣凹,只能硬著頭皮上了。

故事發(fā)生背景: 事情的起因是這樣的恨豁,我需要用 Antd tablefilter 功能嚣镜,來動態(tài)控制展示列,到這都沒啥難的橘蜜,但是 filter 里面的數(shù)據(jù)是通過接口來獲得的菊匿,用戶操作動態(tài)列,點擊重置的時候要求不是清空所有的條件计福,而是回到初進頁面的狀態(tài)跌捆,即用戶沒操作之前的狀態(tài),即:可記住用戶行為。

Antd 官方給的例子??

于是難點就在這里了,根據(jù)官方給出的例子蕊肥,我無法辨別表格篩選條件為空的時候弄诲,是點擊重置按鈕觸發(fā)的還是全部取消之后點擊確認按鈕觸發(fā)的(本文核心要解決的問題)挚冤。這時候無路可選了,只能自己寫一個類似的功能,然后在使用 Antd table 「自定義篩選表頭功能」 集合到 table 上,讓我們開始吧钙姊。

一、create-react-app 使用 less 遇到的坑

用 React 越久 埂伦,我就越來越不喜歡它煞额,最新又用出問題了,我們知道 React 的插件基本沒有官方維護的沾谜,好處當然是遍地開花膊毁,但是壞也很明顯的,就是插件的兼容性對比 Vue 極差类早。

這不在使用 customize-cra 和 react-app-rewired 來定制化 webpack 配置 less 的時候媚媒,就遇到大坑嗜逻,customize-cra 支持 less-loader 的最高版本是 5涩僻,我的少女心??,竟然落后現(xiàn)在兩個大版本。

二逆日、組件封裝 ??

完了嵌巷,demo 代碼我寫完,就去更新 使用 antd@4 table 自定義篩選表頭功能做一個聯(lián)動搜索表頭篩選 這篇文章了室抽,然后這篇文章就被扔下來了搪哪,就差邏輯說明沒寫了??。所以現(xiàn)在就相當于帶著你讀者們一起讀代碼 ??坪圾。

這是我公開發(fā)表的第一篇——使用 hooks 寫的 demo晓折,寫的不好多見諒,順便說下我感覺它最大的缺點:因為閉包影響導致 state 出現(xiàn)的過時狀態(tài)兽泄,使 hooks 不能處理復雜邏輯漓概,還的用 class 組件,最大的好處就是性能控制相比類組件更加精細化了病梢。

封裝實現(xiàn)要求:和 Antd 官網(wǎng) 給出的 可控的篩選和排序 例子一樣胃珍。我們只增加全部取消之后點擊確定按鈕和點擊重置按鈕 ——按鈕事件觸發(fā)時的名字,就是打個標記蜓陌。

第一步:先給布局樣式 filterItem.less 文件:

.common-show-items{
    .ant-popover-inner-content{
        padding: 0 !important;
    }
    .ant-popover{
        padding-top: 0;
        .ant-popover-arrow{
            display: none;
        }
    }
    label{
        display: inline-block;
        width: 100%;
        padding: 5px 12px;
        color: rgba(0, 0, 0, 0.65);
        font-size: 14px;
        line-height: 22px;
        white-space: nowrap;
        cursor: pointer;
        transition: all 0.3s;
        &:hover{
            background-color: #f5f5f5;
        }
        .ant-checkbox-checked .ant-checkbox-inner {
            background-color: #256FEB;
            border-color: #256FEB;
        }
    }
    label.ant-checkbox-wrapper-checked{
        color: #256FEB;
        background-color: #e6f7ff;
    }
    .filter-dropdown-btns{
        display: flex;
        justify-content: space-between;
        padding: 7px 8px 7px 3px;
        overflow: hidden;
        border-top: 1px solid #f0f0f0;
        .ant-btn-primary{
            background: #2A5FF1;
            border-color: #2A5FF1;
        }
        button{
            height: 24px;
            padding: 0px 7px;
            font-size: 14px;
            border-radius: 2px;
        }
    }
}

第二步:filterItem.js 邏輯處理組件源代碼觅彰,已詳盡注釋:

import React, { useState, useCallback, useEffect } from "react";
import { Popover, Checkbox, Button } from "antd";
import "./filterItem.less";

const FilterItem = (props) => {
    const { title, filters, isShowPop, popOverVisibleChange, okText, cancelText, handleChange, filterIcon, initItems = [] } = props;
    const [ displayItems, setDisplayItems ] = useState(initItems);
    const [ showItems, setShowItems ] = useState([]);
    const [ onEvent, setOnEvent ] = useState(false);

    // 圖標變色
    const iconComponent = React.cloneElement(
        filterIcon, {
            style: { color: displayItems.length ? '#1890ff' : undefined }
        }
    );
    
    /* 是否支持外部可控,如果外部可以打開關(guān)閉钮热,就不使用內(nèi)部的可控函數(shù) */
    const [ filteredValue, setFilteredValue ] = useState(false);
    const handleVisibleChange = useCallback(() => {
        setFilteredValue(!filteredValue);
    }, [ filteredValue ]);

    /* 展示組件打開之后備份目前選中的展示項   => 用于點擊確定按鈕關(guān)閉的時候驗證選中項目是否發(fā)生改變 */
    useEffect(() => {
        // console.log(showItems, 99, isShowPop, filteredValue, onEvent);
        if (isShowPop || filteredValue) {
            if (onEvent) return;
            setOnEvent(true);
            setShowItems(displayItems);
        } else {
            setOnEvent(false);
        };
    }, [ isShowPop, filteredValue, onEvent, displayItems, showItems ]);

    // 多選事件
    const handleFilterChange = useCallback(e => {
        setDisplayItems(e);
    }, [ ]);
    
    // 展示checkbox
    const checkboxGroup = useCallback(() => {
        return filters.map(({ text, value }) => <div key={value}><Checkbox value={value} >{text}</Checkbox></div>);
    }, [ filters ]);

    /* 
        核心功能函數(shù)
        1. 展示組件打開之后填抬,無任何操作,直接點擊確定按鈕關(guān)閉,不觸發(fā)事件
        2. 展示組件打開之后隧期,無任何操作痴奏,直接點擊重置按鈕關(guān)閉,不觸發(fā)事件
    */
    const handleItemsEquality = useCallback((displayItems, showOrInitItems, close) => {
        let count = 0, maxSizeItem, minSizeItem;
        /* 
            判斷兩個展示項完全相等邏輯:
            判斷操作的展示項和備份的展示項 及 操作的展示項和初始化的展示項
            找出兩者之間最長的,用最長的那個去遍歷短的厌秒,相等就計數(shù)读拆,最后查看是否完全相等
        */
        if (displayItems.length > showOrInitItems.length) {
            maxSizeItem = displayItems;
            minSizeItem = showOrInitItems;
        } else {
            maxSizeItem = showOrInitItems;
            minSizeItem = displayItems;
        };
        maxSizeItem.forEach(item => {
            if (minSizeItem.includes(item)) count++;
        });
        if (count === maxSizeItem.length){
            // 相等,直接關(guān)閉鸵闪,返回 true 不觸發(fā)回調(diào)
            close();
            return true;
        } else { 
            return false;
        };
    }, [ ]);

    const handleFilterBtns = useCallback(eventName => {
        const close =  popOverVisibleChange ?? handleVisibleChange;
        if (eventName === "OK") {
            const isCanClick = handleItemsEquality(displayItems, showItems, close);
            
            if (isCanClick) return;
            handleChange({ action: eventName, currentShowItem: displayItems, confirm: close });
        } else {
            const isCanClick = handleItemsEquality(displayItems, initItems, close);
            if (isCanClick) return;
            setDisplayItems(initItems);
            setShowItems(initItems);
            handleChange({ action: eventName, currentShowItem: initItems, confirm: close });
        };
    }, [ displayItems, showItems, initItems, handleChange, handleItemsEquality, handleVisibleChange, popOverVisibleChange ]);

    return (
        <>
            <div className="common-show-items">
                <Popover
                    trigger="click"
                    placement="bottomLeft"
                    visible={isShowPop ?? filteredValue}
                    getPopupContainer={(Node) => Node.parentNode}
                    onVisibleChange={popOverVisibleChange ?? handleVisibleChange}
                    content={
                        <div className="filter-group">
                            <Checkbox.Group onChange={handleFilterChange} value={displayItems}>
                                {checkboxGroup()}
                            </Checkbox.Group>
                            <div className="filter-dropdown-btns">
                                <Button type="link" onClick={handleFilterBtns.bind({}, "Reset")}>{cancelText ?? "重置"}</Button>
                                <Button type="primary" onClick={handleFilterBtns.bind({}, "OK")}>{okText ?? "確定"}</Button>
                            </div>
                        </div>
                    } >
                    <Button>{iconComponent ?? null}{title}</Button>
                </Popover>
            </div>
        </>
    );
};


export default FilterItem;

第三步:如何在項目中使用:

import React, { useState, useCallback } from 'react';
import FilterItem from './view/filterItem';
import { SettingOutlined } from '@ant-design/icons';

import './App.css';

function App() {
    /* 1. 外部也可以控制展開收起 */
    // const [ isShowPop, setShowPop ] = useState(false);
    // const popOverVisibleChange = useCallback(() => {
    //     setShowPop(!isShowPop);
    // }, [ isShowPop ]);
    

    const handleChange = useCallback(({ action, currentShowItem, confirm }) => {
        confirm();
        console.log("handleChange", action, currentShowItem);
    }, [  ]);
    return (
        <div className="App">
            <FilterItem
                // isShowPop={isShowPop}
                // popOverVisibleChange={popOverVisibleChange}

                // 2. 使用 okText 和 cancelText 自定義按鈕文字
                // okText="OK"
                // cancelText="Reset"

                // 確認按鈕和重置按鈕正確觸發(fā)的回調(diào)函數(shù)
                handleChange={handleChange}
                // 展示項的名字
                title={"列展示"}
                // 展示項的圖標
                filterIcon={<SettingOutlined />}
                // 默認顯示的展示項
                initItems={["London"]}
                // 展示項列表
                filters={[
                    {
                      text: '倫敦',
                      value: 'London',
                    },
                    {
                      text: '紐約',
                      value: 'New York',
                    },
                    {
                        text: '香港',
                        value: 'Hong Kong',
                    },
                    {
                        text: '阿拉斯加',
                        value: 'Alaska',
                    },
                    {
                        text: '東京',
                        value: 'Tōkyō',
                    },
                    {
                        text: '上海',
                        value: 'shanghai',
                    }
                ]}
            />
        </div>
    );
}

export default App;

第四步:動圖演示

模仿 antd@4 table 自定義篩選表頭功能 => 做一個動態(tài)列 table

動圖演示 filterItems 組件表現(xiàn)出的功能:

  1. 初始化展示項檐晕。
  2. 展開,點擊增加選中項蚌讼,點擊確定按鈕辟灰,觸發(fā)回調(diào)事件,扔出結(jié)果篡石。
  3. 展開芥喇,點擊減少選中項,點擊確定按鈕凰萨,觸發(fā)回調(diào)事件继控,扔出結(jié)果械馆。
  4. 展開,先點擊減少選中項武通,再點擊增加選中項霹崎,需要保持打開時的選中項,最后點擊確定按鈕冶忱,觸發(fā)回調(diào)事件尾菇,扔出結(jié)果。
  5. 展開囚枪,無操作點擊確定按鈕派诬,不觸發(fā)任何事件。
  6. 展開链沼,無操作點擊重置按鈕千埃,不觸發(fā)任何事件。
  7. 點擊空白區(qū)域相當于點擊確定

三忆植、集成到 Antd table 上

太簡單了放可,不寫了,自己琢磨去吧朝刊。

寫作時間 Sunday, September 20, 2020 00:07:38
昨天比現(xiàn)在早一個小時耀里,Vue 3.0 正式版發(fā)布了,就問問你啥感受??????

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末拾氓,一起剝皮案震驚了整個濱河市冯挎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌咙鞍,老刑警劉巖房官,帶你破解...
    沈念sama閱讀 222,865評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異续滋,居然都是意外死亡翰守,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,296評論 3 399
  • 文/潘曉璐 我一進店門疲酌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蜡峰,“玉大人,你說我怎么就攤上這事朗恳∈” “怎么了?”我有些...
    開封第一講書人閱讀 169,631評論 0 364
  • 文/不壞的土叔 我叫張陵粥诫,是天一觀的道長油航。 經(jīng)常有香客問我,道長怀浆,這世上最難降的妖魔是什么谊囚? 我笑而不...
    開封第一講書人閱讀 60,199評論 1 300
  • 正文 為了忘掉前任怕享,我火速辦了婚禮,結(jié)果婚禮上秒啦,老公的妹妹穿的比我還像新娘。我一直安慰自己搀玖,他們只是感情好余境,可當我...
    茶點故事閱讀 69,196評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著灌诅,像睡著了一般芳来。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猜拾,一...
    開封第一講書人閱讀 52,793評論 1 314
  • 那天即舌,我揣著相機與錄音,去河邊找鬼挎袜。 笑死顽聂,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的盯仪。 我是一名探鬼主播紊搪,決...
    沈念sama閱讀 41,221評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼全景!你這毒婦竟也來了耀石?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,174評論 0 277
  • 序言:老撾萬榮一對情侶失蹤爸黄,失蹤者是張志新(化名)和其女友劉穎滞伟,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體炕贵,經(jīng)...
    沈念sama閱讀 46,699評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡梆奈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,770評論 3 343
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了称开。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鉴裹。...
    茶點故事閱讀 40,918評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖钥弯,靈堂內(nèi)的尸體忽然破棺而出径荔,到底是詐尸還是另有隱情,我是刑警寧澤脆霎,帶...
    沈念sama閱讀 36,573評論 5 351
  • 正文 年R本政府宣布总处,位于F島的核電站,受9級特大地震影響睛蛛,放射性物質(zhì)發(fā)生泄漏鹦马。R本人自食惡果不足惜胧谈,卻給世界環(huán)境...
    茶點故事閱讀 42,255評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望荸频。 院中可真熱鬧菱肖,春花似錦、人聲如沸旭从。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,749評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽和悦。三九已至退疫,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸽素,已是汗流浹背褒繁。 一陣腳步聲響...
    開封第一講書人閱讀 33,862評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留馍忽,地道東北人棒坏。 一個月前我還...
    沈念sama閱讀 49,364評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像遭笋,于是被迫代替她去往敵國和親俊抵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,926評論 2 361