下面是多字段排序逻辑封装成一个通用的工具函数形式,放入一个 SortUtils 工具类中,方便在项目中复用。
SortUtils.sortBy` 实现(支持泛型、类型安全)
type SortOrder = 'asc' | 'desc';
interface SortKeyConfig<T> {
key: keyof T;
order: SortOrder;
}
type SortKey<T> = keyof T | SortKeyConfig<T>;
interface SortConfig<T> {
keys: SortKey<T>[];
defaultOrder?: SortOrder;
}
export class SortUtils {
/**
* 多字段排序函数
* @param data 需要排序的数据数组
* @param config 排序配置项
* @returns 排序后的新数组(不会修改原数组)
*/
static sortBy<T>(data: T[], config: SortConfig<T>): T[] {
const defaultOrder: SortOrder = config.defaultOrder ?? 'asc';
return [...data].sort((a, b) => {
for (const item of config.keys) {
const key = typeof item === 'string' ? item : item.key;
const order = typeof item === 'string' ? defaultOrder : item.order;
const aValue = a[key];
const bValue = b[key];
if (aValue !== bValue) {
if (typeof aValue === 'string' && typeof bValue === 'string') {
return order === 'asc'
? aValue.localeCompare(bValue)
: bValue.localeCompare(aValue);
}
if (typeof aValue === 'number' && typeof bValue === 'number') {
return order === 'asc' ? aValue - bValue : bValue - aValue;
}
if (aValue instanceof Date && bValue instanceof Date) {
return order === 'asc'
? aValue.getTime() - bValue.getTime()
: bValue.getTime() - aValue.getTime();
}
return order === 'asc'
? String(aValue).localeCompare(String(bValue))
: String(bValue).localeCompare(String(aValue));
}
}
return 0;
});
}
}
使用示例
import { SortUtils } from './utils/SortUtils';
interface Person {
name: string;
age: number;
birthday: Date;
}
const people: Person[] = [
{ name: 'Alice', age: 30, birthday: new Date('1994-01-01') },
{ name: 'Bob', age: 25, birthday: new Date('1999-01-01') },
{ name: 'Charlie', age: 25, birthday: new Date('1998-01-01') }
];
// 先按年龄升序,再按生日降序
const sorted = SortUtils.sortBy(people, {
keys: [
{ key: 'age', order: 'asc' },
{ key: 'birthday', order: 'desc' }
]
});
TypeScript 多字段排序 Demo 多字段排序实现(类似 C# orderBy 和 thenBy)
enum SortDirection {
Ascending = 'asc',
Descending = 'desc'
}
interface SortCondition<T> {
selector: (item: T) => any;
direction: SortDirection;
}
class OrderBuilder<T> {
private readonly source: T[];
private readonly conditions: SortCondition<T>[] = [];
private resultCache: T[] | null = null;
constructor(source: T[]) {
this.source = source;
}
orderBy<K>(selector: (item: T) => K, direction: SortDirection = SortDirection.Ascending): OrderBuilder<T> {
this.conditions.length = 0;
this.conditions.push({ selector, direction });
this.resultCache = null;
return this;
}
orderByDescending<K>(selector: (item: T) => K): OrderBuilder<T> {
return this.orderBy(selector, SortDirection.Descending);
}
thenBy<K>(selector: (item: T) => K, direction: SortDirection = SortDirection.Ascending): OrderBuilder<T> {
this.conditions.push({ selector, direction });
this.resultCache = null;
return this;
}
thenByDescending<K>(selector: (item: T) => K): OrderBuilder<T> {
return this.thenBy(selector, SortDirection.Descending);
}
toArray(): T[] {
if (this.resultCache) {
return this.resultCache;
}
if (this.conditions.length === 0) {
this.resultCache = [...this.source];
return this.resultCache;
}
this.resultCache = [...this.source].sort((a, b) => {
for (const condition of this.conditions) {
const valueA = condition.selector(a);
const valueB = condition.selector(b);
if (valueA === valueB) {
continue;
}
// 针对数值类型的优化
if (typeof valueA === 'number' && typeof valueB === 'number') {
return condition.direction === SortDirection.Ascending
? valueA - valueB
: valueB - valueA;
}
const result = valueA < valueB ? -1 : 1;
return condition.direction === SortDirection.Ascending ? result : -result;
}
return 0;
});
return this.resultCache;
}
}
// 辅助函数
export function orderBy<T, K>(source: T[], selector: (item: T) => K, direction: SortDirection = SortDirection.Ascending): OrderBuilder<T> {
return new OrderBuilder(source).orderBy(selector, direction);
}
export function orderByDescending<T, K>(source: T[], selector: (item: T) => K): OrderBuilder<T> {
return orderBy(source, selector, SortDirection.Descending);
}
export { SortDirection, OrderBuilder };
创建一个示例文件来演示如何使用这个排序工具:
import { orderBy, orderByDescending, SortDirection } from './OrderBuilder';
// 定义数据接口
interface Person {
id: number;
name: string;
age: number;
salary: number;
department: string;
}
// 创建示例数据
const people: Person[] = [
{ id: 1, name: "张三", age: 30, salary: 10000, department: "技术部" },
{ id: 2, name: "李四", age: 25, salary: 8000, department: "市场部" },
{ id: 3, name: "王五", age: 30, salary: 12000, department: "技术部" },
{ id: 4, name: "赵六", age: 25, salary: 9000, department: "人事部" },
{ id: 5, name: "钱七", age: 35, salary: 15000, department: "技术部" },
{ id: 6, name: "孙八", age: 28, salary: 11000, department: "市场部" },
{ id: 7, name: "周九", age: 35, salary: 14000, department: "人事部" },
{ id: 8, name: "吴十", age: 28, salary: 13000, department: "技术部" },
];
// 示例1: 按年龄升序,然后按薪资降序排序
console.log("示例1: 按年龄升序,然后按薪资降序排序");
const result1 = orderBy(people, p => p.age)
.thenByDescending(p => p.salary)
.toArray();
console.table(result1);
// 示例2: 按部门升序,然后按年龄降序,最后按姓名升序排序
console.log("\n示例2: 按部门升序,然后按年龄降序,最后按姓名升序排序");
const result2 = orderBy(people, p => p.department)
.thenByDescending(p => p.age)
.thenBy(p => p.name)
.toArray();
console.table(result2);