模仿 antd@4 table 自定义筛选表头功能 => 做一个
前言: 如果你经常写东西你就会发现,写的东西涉及原理和新知识、等需要主动学习的,阅读量特别的低,阅读量最高的基本是框架如何使用和使用中遇到哪些坑这种文章。我是不太喜欢去很深入的研究框架在项目中的用法,但是没办法平时写项目就的用到,只能硬着头皮上了。
故事发生背景: 事情的起因是这样的,我需要用 Antd table
的 filter
功能,来动态控制展示列,到这都没啥难的,但是 filter 里面的数据是通过接口来获得的,用户操作动态列,点击重置的时候要求不是清空所有的条件,而是回到初进页面的状态,即用户没操作之前的状态,即:可记住用户行为。
于是难点就在这里了,根据官方给出的例子,我无法辨别表格筛选条件为空的时候,是点击重置按钮触发的还是全部取消之后点击确认按钮触发的(本文核心要解决的问题)。这时候无路可选了,只能自己写一个类似的功能,然后在使用 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
组件表现出的功能:
- 初始化展示项。
- 展开,点击增加选中项,点击确定按钮,触发回调事件,扔出结果。
- 展开,点击减少选中项,点击确定按钮,触发回调事件,扔出结果。
- 展开,先点击减少选中项,再点击增加选中项,需要保持打开时的选中项,最后点击确定按钮,触发回调事件,扔出结果。
- 展开,无操作点击确定按钮,不触发任何事件。
- 展开,无操作点击重置按钮,不触发任何事件。
- 点击空白区域相当于点击确定
三、集成到 Antd table 上
太简单了,不写了,自己琢磨去吧。
写作时间 Sunday, September 20, 2020 00:07:38
昨天比现在早一个小时,Vue 3.0 正式版发布了,就问问你啥感受😂😂😂