分享两个多字段排序工具函数sortBy

下面是多字段排序逻辑封装成一个通用的工具函数形式,放入一个 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);
4赞

看起来很方便 字符串也排序好

mark了,去收藏夹里躺着吧。