表格有一个常用的功能就是排序功能,排序功能依据谁排分为两种:
前端排序(简单的序号等)
后端排序(复杂点的排序,一般依赖数据库的排序功能)
多个表头排序依据之间有没有分为:
单列排序(互不影响)
多列排序(根据点击顺序联动排序,注意 Antd3 不支持多列排序)
一、掌握基础
以下摘录 Antd 官网,静静的看一遍全部搞懂:
- 筛选和排序
对某一列数据进行筛选,使用列的 filters 属性来指定需要筛选菜单的列,onFilter 用于筛选当前数据,filterMultiple 用于指定筛选条件是多选和单选。(筛选不是排序不看)
重点来了:
对某一列数据进行排序,通过指定列的 sorter 函数即可启动排序按钮。
sorter: function(rowA, rowB) { ... },
rowA、rowB 为比较的两个行数据。(亲测排序条件只要为真,排序就会生效,不一定非要为函数)
sortDirections: ['ascend' | 'descend']
改变每列可用的排序方式,切换排序时按数组内容依次切换,设置在 table props 上时对所有列生效。(sortDirections: ['ascend']
这样设置只会有一个升序排序)
使用 defaultSortOrder 属性,设置列的默认排序顺序。
- 多列排序
column.sorter
支持multiple
字段以配置多列排序优先级。通过sorter.compare
配置排序逻辑,你可以通过不设置该函数只启动多列排序的交互形式。
格式如下:
{
title: 'Chinese Score',
dataIndex: 'chinese',
sorter: {
compare: (a, b) => a.chinese - b.chinese,
multiple: 3,
},
},
multiple
表示多列排序的顺序,纯前端排序的话,要注意 multiple
的值,比如我点了 multiple: 1,
的排序,然后点了 multiple: 2,
的排序,最终结果会这样显示,先按 1 条件排,拍完之后里面重的内容,再按 2 的条件排列。
- 可控的筛选和排序
使用受控属性对筛选和排序状态进行控制。
- columns 中定义了 filteredValue 和 sortOrder 属性即视为受控模式。(筛选不是排序不看)
- 只支持同时对一列进行排序,请保证只有一列的 sortOrder 属性是生效的。(重点:这句话是不对的,这句话成立的条件是,单列可控排序。多列可控排序多个 sortOrder 属性可以同时生效)
- 务必指定
column.key
(这个是 React 需要的 key,如果已经设置了唯一的 dataIndex,可以忽略这个属性,所以不用排序的时候我们一般都不会特意指定 columnKey 的值,但是这里为什么要求务必指定呢?看官网可控组件的例子,它是这么写的)。sortOrder: sortedInfo.columnKey === 'name' && sortedInfo.order,
通过 columnKey 来判断某一列的排序,要求指定 columnKey 其实就是为了可控组件排序
补充第三点的内容:1. 正常情况下使用 Antd 的 Table,在给列数据的时候,官方建议指定dataIndex
和columnKey
,这两个完全可以就写一个,未必要这个麻烦,当然官方说明在已经设置了dataIndex
可以省略columnKey
。2. 至于可控排序务必指定columnKey
也是没有必要,因为当你不指定的时候,点击排序 onChange 的 sorter 参数内容是这样的{column: {…}, order: "ascend", field: "name", columnKey: "name"}
,完全没影响,而且 filed 也可以替代 columnKey。
总结:columnKey
完全没有指定的必要,可控排序通过filed
替代columnKey
变量。
写到下面才发现 columnKey 和 key 搞混了。真实的结论应该是:不使用可控排序,在指定 dataIndex 情况下可以省略给 React 使用的 key ,使用可控排序,请指定 key ,这样 onChange
函数 sorter
里面的 columnKey 才会有值,再去控制 sorterOrder
。
-
onChange
事件
触发条件:分页、排序、筛选变化时触发,Function(pagination, filters, sorter, extra: { currentDataSource: [] })
,前三个参数好理解,最后一个参数extra
表示当前表格的数据,也就是 dataSource 的内容。
好了,以上内容搞懂,我们来看看多列可控排序配合分页出现的小 bug(我认为是 bug,可能官网就是这样设计的)。
二、多列可控排序配合分页出现的小 bug
先给出项目代码:
import React , { Component , Fragment } from 'react';
import ReactDOM from "react-dom";
import "antd/dist/antd.min.css";
import { Table , Space , Button , Input } from 'antd';
export default class App extends Component {
constructor(props){
super(props);
this.state = {
pageSize: 20
};
this.onChange = this.onChange.bind(this);
this.clearSorterMulti = this.clearSorterMulti.bind(this);
this.getQuery = this.getQuery.bind(this);
}
getQuery(){
console.log("getQuery");
}
clearSorterMulti(){
console.log("clearSorterMulti");
}
onChange(pagination, filters, sorter, extra) {
const { pageSize } = pagination;
this.setState({
pageSize
})
console.log(sorter);
}
render() {
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
sorter: {
multiple: 0
},
sortOrder: false,
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
sorter: {
multiple: 1
},
sortOrder: false,
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
sorter: {
multiple: 2
},
sortOrder: false,
},
];
const data = new Array(100).fill(Math.random().toFixed(3)).map((item, index) => (
{
key: index,
name: item,
age: item,
address: item,
}
))
return (
)
}
}
ReactDOM.render(
, document.getElementById('root'));
效果图应该是这样:
接下来我们只关心改变 sortOrder
的值,点击翻页 onChange
函数 sorter
参数的变化:
- 当没有
sortOrder
的时候,也就是不可控排序
在没有点击表头排序的情况下,点击分页,点击翻页 onChange
函数 sorter
参数是个空对象:{}
。注意 ⚠️ 如果点击表头 onChange
函数 sorter
参数是正常的。
- 当
sortOrder
的值为假
在没有点击表头排序的情况下,点击分页,点击翻页 onChange
函数 sorter
参数是第一个可控排序的对象:{column: undefined, order: false, field: "name", columnKey: "name"}
- 当
sortOrder
的值为真
在没有点击表头排序的情况下,点击分页,点击翻页 onChange
函数 sorter
参数是所有 sorterOrder
为真的集合(如果只有一个 sorterOrder
为真,sorter
参数是一个对象,如果有多个 sorterOrder 为真, sorter
参数是一个包含 sorterOrder
为 true 所有对象的一个数组)
当我们设置所有的 sorterOrder
为真打印出来的 sorter
如下:
[
{"column": {"title": "Name","dataIndex": "name", "key": "name", "sorter": { "multiple": 0 }, "sortOrder": true }, "order": true, "field": "name", "columnKey": "name" },
{ "column": { "title": "Age", "dataIndex": "age", "key": "age", "sorter": { "multiple": 1 }, "sortOrder": true }, "order": true, "field": "age", "columnKey": "age" },
{ "column": { "title": "Address", "dataIndex": "address", "key": "address", "sorter": { "multiple": 2 }, "sortOrder": true }, "order": true, "field": "address", "columnKey": "address" }
]
补充上面的数据我是如何复制过来的?首先:更改
console.log(window.a = sorter);
,然后在控制台输出一下变量a
,再在控制台运行代码:copy($_)
把代码复制到剪切板,回到 VSCode 里通过快捷键ctrl+j
压平。
-
sortOrder
其他情况分析
onChange
函数sorter
能打印出来结果的前提是sortOrder
为真,但是sortOrder
必须为"ascend"
或"descend"
排序才能生效。单列排序的时候
onChange
函数sorter
永远是个对象。多列排序的时候,只点击了一次排序表头,
onChange
函数sorter
是个对象,点击了多个排序表头,onChange
函数sorter
是个数组。
三、对接后台进行多列顺序排序
多列就不用多讲了,主要是顺序,我们要求最后点击排序的权重最低,很明显我们要维护一个数组。这个数组大概长成这样:[NAME_ASC,AGE_DESC,ADDRESS_DESC]
,演示排序字段如下:
/*
NAME_ASC 名字正序
NAME_DESC 名字倒序
AGE_ASC
AGE_DESC
ADDRESS_ASC
ADDRESS_DESC
*/
后台排序发送接口是 query 形式的,但不是 a=descend&b=ascend
形式,而是直接发送一个数组,直接表现结果为:sortKey=a,b,c,d
。两种形式大同小异,没啥区别。
先把难点给讲了:难点就是维护排序的数组,何时增,何时减,何时替换。
何时增? 排序数组的长度大于我们维护排序数组的长度,是增加查询条件。
何时替换?排序数组的长度等于我们维护排序数组的长度,是替换查询条件。
何时减?排序数组的长度小于我们维护排序数组的长度,是减少查询条件。
除此之外,你会发现开头我们演示页面上带了两个按钮,表示查询带上排序条件,重置清空排序条件。
源代码:
import React, { Component, Fragment } from 'react';
import ReactDOM from "react-dom";
import "antd/dist/antd.min.css";
import { Table, Space, Button, Input } from 'antd';
/*
NAME_ASC 名字正序
NAME_DESC 名字倒序
AGE_ASC
AGE_DESC
ADDRESS_ASC
ADDRESS_DESC
*/
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
pageSize: 20,
/* 顺序排列的数组 */
sorterKey: [],
sorterArr: [],
};
this.onChange = this.onChange.bind(this);
this.clearSorterMulti = this.clearSorterMulti.bind(this);
this.getQuery = this.getQuery.bind(this);
}
getQuery() {
console.log("getQuery")
/*此处携带 sorterKey 和 input 输入的查询条件,发送Ajax然后setState更新 */
}
clearSorterMulti() {
/*此处发Ajax然后setState更新 */
this.setState({
sorterKey: [],
sorterArr: [],
});
}
addReplaceQuery( queryName, sorterKey, sortOrder ){
sorterKey = sorterKey.filter( item=>!item.includes(queryName))
/* 最后一次取消的时候,sorter随机返回 sorterKey置空*/
sortOrder.order ? sorterKey.push( sortOrder.order === "ascend" ? `${queryName}ASC` : `${queryName}DESC` ) : (sorterKey = []);
console.log(sorterKey)
return sorterKey;
}
onChange(pagination, filters, sorter, extra) {
console.log(sorter)
/* sorter可能是个组数也可能不是个数组,我们统一按数组处理 */
const sorterArr = Array.isArray(sorter) ? sorter : [ sorter ];
let sorterKey = JSON.parse(JSON.stringify(this.state.sorterKey));
const { pageSize } = pagination;
/* 排序数组sorterKey */
/* 增加替换条件逻辑 */
if(sorterArr.length >= sorterKey.length){
/* 获取最新一次排序的对象 */
const sortOrder = sorterArr[ sorterArr.length - 1 ];
switch(sortOrder && sortOrder.columnKey){
case "NAME":
sorterKey = this.addReplaceQuery( "NAME_", sorterKey, sortOrder);
break;
case "AGE":
sorterKey = this.addReplaceQuery( "AGE_", sorterKey, sortOrder);
break;
case "ADDRESS":
sorterKey = this.addReplaceQuery( "ADDRESS_", sorterKey, sortOrder);
break;
default:
break;
}
}else{
/* 减条件逻辑 */
const arr = [];
sorterArr.forEach(item=>sorterKey.forEach((key,idx)=> key.includes(item.columnKey) && arr.push(key) ));
sorterKey = arr;
}
/*此处发Ajax然后setState更新 */
this.setState({
pageSize,
sorterArr,
sorterKey,
})
}
render() {
const { sorterArr = [] } = this.state;
/* sorterArr 是个数组,处理下格式*/
const sorterInfo = {};
sorterArr.forEach((item,index)=>item && (sorterInfo[item["field"]] = item["field"]) && (sorterInfo[item[["columnKey"]]] = item["order"]));
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'NAME',
sorter: {
multiple: 0
},
sortOrder: sorterInfo.name === "name" && sorterInfo.NAME,
},
{
title: 'Age',
dataIndex: 'age',
key: 'AGE',
sorter: {
multiple: 1
},
sortOrder: sorterInfo.age === "age" && sorterInfo.AGE,
},
{
title: 'Address',
dataIndex: 'address',
key: 'ADDRESS',
sorter: {
multiple: 2
},
sortOrder: sorterInfo.address === "address" && sorterInfo.ADDRESS,
},
];
const data = new Array(100).fill(Math.random().toFixed(3)).map((item, index) => (
{
key: index,
name: item,
age: item,
address: item,
}
))
return (
)
}
}
ReactDOM.render(
, document.getElementById('root'));
总结:现在捋一捋思路,我感觉逻辑变的更加清晰了,我第一次在项目中写的挺乱的,我还记得测试测出问题,我脑子都懵了,愣是不明白删除查询条件逻辑错误,我写的是既然最新点击是数组的最后一个值,那我删除的时候,就直接数组 pop
好了。这个逻辑最大的问题就是点击最新的排序不能停,直至它被删除,但是这有个问题就是,我依次点击 A、B、C 三列进行排序,A 现在是降序,再点一次就排序就失效了,但是最后一次点击的是 C 。这时我点击 A 就会 pop 了 C 的排序。当时愣是没检查出来,而且最奇葩的是我连 if 语句都不会写了。我记得当时是这么写的 if -else ,惊呆了我的前端小伙伴:
if(query.queryData.queryAction === 'search'){
//这里发生了什么 ** **
}else{
if(sortKeys.length <= 1){
sortKeys.pop();
}else{
const sorterRouter = {
PROMOTION_BEGIN_DATE_ASC: 'beginDate',
PROMOTION_BEGIN_DATE_DESC: 'beginDate',
PROMOTION_END_DATE_ASC: 'endDate',
PROMOTION_END_DATE_DESC: 'endDate',
PROMOTION_TYPE_ASC: 'type',
PROMOTION_TYPE_DESC: 'type',
};
sortKeys = sortKeys.filter(item=>(sortArr.map(item=>item.field)).includes(sorterRouter[ item ]));
}
}
写作时间: Sunday, June 21, 2020 02:02:44