TypeScript按接口属性级联优先级排序对象数组的通用方案问询
TypeScript 同接口对象数组的类型安全级联优先级排序方案
问题背景
需要对实现同一接口的对象数组实现级联优先级排序:先按指定属性排序,若该属性值相等,则按第二个属性排序,以此类推。同时要求保证类型安全,避免出现属性类型与排序器类型不匹配的错误(比如用数字排序器处理日期属性)。
现有尝试的问题
已定义基础排序类型与规则接口:
type Sorter<T> = (a: T, b: T) => number; const stateSorter = (aState: number, bState: number) => (bState < aState? -1 : 1); const dateSorter = (aDate: Date, bDate: Date) => (aDate >= bDate ? -1 : 1); interface SortSupplier<T, R> { supplier: (container: T) => R; sorter: Sorter<R>; } interface StateAndDate { stateId: ClientStateIdEnum; registrationDate: Date; }
但之前的实现存在问题:
- 使用
SortSupplier<T, any>数组:类型不严谨,无法在编译时拦截属性与排序器类型不匹配的错误。 - 使用联合类型约束
SortSupplier:出现类型兼容错误(Argument of type 'number | Date' is not assignable to parameter of type 'Date & number'),无法正常编译。
通用类型安全解决方案
通过泛型元组约束排序规则数组,实现类型安全的级联排序:
1. 完善基础类型定义
type Sorter<T> = (a: T, b: T) => number; // 数字降序排序器 const stateSorter: Sorter<number> = (a, b) => b < a ? -1 : 1; // 日期降序排序器 const dateSorter: Sorter<Date> = (a, b) => a >= b ? -1 : 1; interface SortSupplier<T, R> { supplier: (item: T) => R; sorter: Sorter<R>; } // 示例枚举与接口 enum ClientStateIdEnum { Active = 1, Inactive = 2, Pending = 3 } interface StateAndDate { stateId: ClientStateIdEnum; registrationDate: Date; }
2. 实现类型安全的级联排序函数
/** * 类型安全的级联排序函数 * @param arr 待排序的对象数组(原数组不会被修改) * @param sorters 排序规则元组,按优先级从高到低排列 * @returns 排序后的新数组 */ function cascadeSort<T, S extends SortSupplier<T, any>[]>( arr: T[], sorters: [...S] ): T[] { return [...arr].sort((a, b) => { // 按优先级依次执行排序规则,直到找到非0的排序结果 for (const { supplier, sorter } of sorters) { const sortResult = sorter(supplier(a), supplier(b)); if (sortResult !== 0) { return sortResult; } } return 0; }); }
3. 使用示例
// 定义排序规则:先按stateId降序,再按registrationDate降序 const sortRules = [ { supplier: (item: StateAndDate) => item.stateId, sorter: stateSorter }, { supplier: (item: StateAndDate) => item.registrationDate, sorter: dateSorter } ] as const; // 使用as const让TypeScript推断精确的元组类型 // 测试数据 const testData: StateAndDate[] = [ { stateId: ClientStateIdEnum.Inactive, registrationDate: new Date('2023-01-01') }, { stateId: ClientStateIdEnum.Active, registrationDate: new Date('2023-03-01') }, { stateId: ClientStateIdEnum.Active, registrationDate: new Date('2023-02-01') }, { stateId: ClientStateIdEnum.Pending, registrationDate: new Date('2023-01-15') } ]; // 执行排序 const sortedData = cascadeSort(testData, sortRules);
4. 简化排序规则定义(可选)
可以封装工具函数快速生成排序规则,减少重复代码:
/** * 快速生成属性排序规则的工具函数 * @param key 对象的属性名 * @param sorter 对应属性类型的排序器 */ function createSortRule<T, K extends keyof T>( key: K, sorter: Sorter<T[K]> ): SortSupplier<T, T[K]> { return { supplier: item => item[key], sorter }; } // 使用工具函数定义规则 const simplifiedSortRules = [ createSortRule('stateId', stateSorter), createSortRule('registrationDate', dateSorter) ] as const; const sortedDataWithUtil = cascadeSort(testData, simplifiedSortRules);
方案优势
- 类型安全:通过泛型元组约束,TypeScript会自动检查每个排序规则的属性类型与排序器类型是否匹配,编译时直接拦截错误。
- 逻辑清晰:按优先级依次执行排序规则,符合级联排序的需求。
- 灵活性高:支持任意数量的排序规则,适配不同的级联排序场景。
内容的提问来源于stack exchange,提问作者PacoBlanco
相关产品推荐
相关产品推荐

