美文网首页React基础前端
模仿 antd@4 table 自定义筛选表头功能 => 做一个

模仿 antd@4 table 自定义筛选表头功能 => 做一个

作者: CondorHero | 来源:发表于2020-09-20 00:08 被阅读0次

    前言: 如果你经常写东西你就会发现,写的东西涉及原理和新知识、等需要主动学习的,阅读量特别的低,阅读量最高的基本是框架如何使用和使用中遇到哪些坑这种文章。我是不太喜欢去很深入的研究框架在项目中的用法,但是没办法平时写项目就的用到,只能硬着头皮上了。

    故事发生背景: 事情的起因是这样的,我需要用 Antd tablefilter 功能,来动态控制展示列,到这都没啥难的,但是 filter 里面的数据是通过接口来获得的,用户操作动态列,点击重置的时候要求不是清空所有的条件,而是回到初进页面的状态,即用户没操作之前的状态,即:可记住用户行为。

    Antd 官方给的例子🌰

    于是难点就在这里了,根据官方给出的例子,我无法辨别表格筛选条件为空的时候,是点击重置按钮触发的还是全部取消之后点击确认按钮触发的(本文核心要解决的问题)。这时候无路可选了,只能自己写一个类似的功能,然后在使用 Antd table 「自定义筛选表头功能」 集合到 table 上,让我们开始吧。

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

    用 React 越久 ,我就越来越不喜欢它,最新又用出问题了,我们知道 React 的插件基本没有官方维护的,好处当然是遍地开花,但是坏也很明显的,就是插件的兼容性对比 Vue 极差。

    这不在使用 customize-cra 和 react-app-rewired 来定制化 webpack 配置 less 的时候,就遇到大坑,customize-cra 支持 less-loader 的最高版本是 5,我的少女心❤️,竟然落后现在两个大版本。

    二、组件封装 📦

    完了,demo 代码我写完,就去更新 使用 antd@4 table 自定义筛选表头功能做一个联动搜索表头筛选 这篇文章了,然后这篇文章就被扔下来了,就差逻辑说明没写了🤥。所以现在就相当于带着你读者们一起读代码 😂。

    这是我公开发表的第一篇——使用 hooks 写的 demo,写的不好多见谅,顺便说下我感觉它最大的缺点:因为闭包影响导致 state 出现的过时状态,使 hooks 不能处理复杂逻辑,还的用 class 组件,最大的好处就是性能控制相比类组件更加精细化了。

    封装实现要求:和 Antd 官网 给出的 可控的筛选和排序 例子一样。我们只增加全部取消之后点击确定按钮和点击重置按钮 ——按钮事件触发时的名字,就是打个标记。

    第一步:先给布局样式 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 }
            }
        );
        
        /* 是否支持外部可控,如果外部可以打开关闭,就不使用内部的可控函数 */
        const [ filteredValue, setFilteredValue ] = useState(false);
        const handleVisibleChange = useCallback(() => {
            setFilteredValue(!filteredValue);
        }, [ filteredValue ]);
    
        /* 展示组件打开之后备份目前选中的展示项   => 用于点击确定按钮关闭的时候验证选中项目是否发生改变 */
        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 ]);
    
        /* 
            核心功能函数
            1. 展示组件打开之后,无任何操作,直接点击确定按钮关闭,不触发事件
            2. 展示组件打开之后,无任何操作,直接点击重置按钮关闭,不触发事件
        */
        const handleItemsEquality = useCallback((displayItems, showOrInitItems, close) => {
            let count = 0, maxSizeItem, minSizeItem;
            /* 
                判断两个展示项完全相等逻辑:
                判断操作的展示项和备份的展示项 及 操作的展示项和初始化的展示项
                找出两者之间最长的,用最长的那个去遍历短的,相等就计数,最后查看是否完全相等
            */
            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){
                // 相等,直接关闭,返回 true 不触发回调
                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"
    
                    // 确认按钮和重置按钮正确触发的回调函数
                    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 自定义筛选表头功能 => 做一个动态列 table

    动图演示 filterItems 组件表现出的功能:

    1. 初始化展示项。
    2. 展开,点击增加选中项,点击确定按钮,触发回调事件,扔出结果。
    3. 展开,点击减少选中项,点击确定按钮,触发回调事件,扔出结果。
    4. 展开,先点击减少选中项,再点击增加选中项,需要保持打开时的选中项,最后点击确定按钮,触发回调事件,扔出结果。
    5. 展开,无操作点击确定按钮,不触发任何事件。
    6. 展开,无操作点击重置按钮,不触发任何事件。
    7. 点击空白区域相当于点击确定

    三、集成到 Antd table 上

    太简单了,不写了,自己琢磨去吧。

    写作时间 Sunday, September 20, 2020 00:07:38
    昨天比现在早一个小时,Vue 3.0 正式版发布了,就问问你啥感受😂😂😂

    相关文章

      网友评论

        本文标题:模仿 antd@4 table 自定义筛选表头功能 => 做一个

        本文链接:https://www.haomeiwen.com/subject/kwmaxktx.html