如何让SortItemConfig的compareFn参数类型与field返回类型自动匹配?
解决TypeScript排序配置中field与compareFn的类型一致性问题
现有类型定义
当前用于排序配置的TypeScript类型如下:
export type SortConfig<T> = ReadonlyArray<SortItemConfig<T>>; export interface SortItemConfig<T> { field: (obj: T) => any; direction: SortDirection; compareFn?: SortFn<any>; } export const SORT_DIRECTIONS = { ASC: 'asc', DESC: 'desc', } as const; export type ValueOf<T> = T[keyof T]; export type SortDirection = ValueOf<typeof SORT_DIRECTIONS>; export type SortFn<T> = (a: T, b: T) => number;
需求目标
希望SortItemConfig中field的返回类型与compareFn的输入类型自动保持一致。例如在以下示例中:
interface Player { name: string; character: 'human' | 'alien'; rank: number; stats: PlayerStats; } interface PlayerStats { wins: number; loses: number; totalMatches: number; notes?: string; } const sortConfig: SortConfig<Player> = [ { field: (player) => player.rank, direction: SORT_DIRECTIONS.ASC, // 期望rank1和rank2自动推断为number类型 compareFn: (rank1, rank2) => { return rank1 - rank2; } }, { field: (player) => player.stats, direction: SORT_DIRECTIONS.ASC, // 期望stat1和stat2自动推断为PlayerStats类型 compareFn: (stat1, stat2) => { return stat1.wins - stat2.wins; } } ];
尝试过的无效方案
曾尝试为SortItemConfig添加泛型参数R,但由于SortConfig直接指定R为any,导致类型推断失效:
export type SortConfig<T> = ReadonlyArray<SortItemConfig<T, any>>; export interface SortItemConfig<T, R> { field: (obj: T) => R; direction: SortDirection; compareFn?: SortFn<R>; }
实际使用场景(更新说明)
排序配置会被用于Sorting<T>类,生成针对T类型的排序函数:
export class Sorting<T> { private readonly sortConfig: SortConfig<T>; constructor(sortConfig: SortConfig<T>) { this.sortConfig = sortConfig; } public getSortFn() { return (objA: T, objB: T): number => { for (const sort of this.sortConfig) { const compare = this.getCompareFn(sort); let result = compare(sort.field(objA), sort.field(objB)); result = isAscending(sort.direction) ? result : -result; if (result !== 0) { return result; } } return 0; }; } protected getCompareFn(sort: SortItemConfig<T>): SortFn<any> { return sort.compareFn ?? compareValues; } } function isAscending(sortDirection: SortDirection): boolean { return sortDirection === SORT_DIRECTIONS.ASC; } function compareValues(a: unknown, b: unknown): number { // 占位实现 return -1; }
正确解决方案
1. 修改类型定义
保留SortItemConfig的泛型参数R,并将SortConfig定义为支持任意R的数组类型:
export type SortConfig<T> = ReadonlyArray<SortItemConfig<T, unknown>>; export interface SortItemConfig<T, R> { field: (obj: T) => R; direction: SortDirection; compareFn?: SortFn<R>; }
2. 添加辅助推断函数
直接声明SortConfig<T>变量会导致TypeScript无法推断每个元素的具体R类型,因此需要创建一个辅助函数来触发逐个元素的类型推断:
export function createSortConfig<T>(config: readonly SortItemConfig<T, any>[]): SortConfig<T> { return config; }
3. 使用辅助函数创建配置
通过createSortConfig函数创建排序配置,TypeScript会自动为每个元素推断field返回类型与compareFn输入类型的一致性:
const sortConfig = createSortConfig<Player>([ { field: (player) => player.rank, direction: SORT_DIRECTIONS.ASC, compareFn: (rank1, rank2) => rank1 - rank2 // rank1、rank2自动推断为number }, { field: (player) => player.stats, direction: SORT_DIRECTIONS.ASC, compareFn: (stat1, stat2) => stat1.wins - stat2.wins // stat1、stat2自动推断为PlayerStats } ]);
4. 更新Sorting类类型(可选)
为了让Sorting类的类型更严谨,可以修改getCompareFn为泛型方法:
export class Sorting<T> { private readonly sortConfig: SortConfig<T>; constructor(sortConfig: SortConfig<T>) { this.sortConfig = sortConfig; } public getSortFn() { return (objA: T, objB: T): number => { for (const sort of this.sortConfig) { const compare = this.getCompareFn(sort); const valA = sort.field(objA); const valB = sort.field(objB); let result = compare(valA, valB); result = isAscending(sort.direction) ? result : -result; if (result !== 0) { return result; } } return 0; }; } protected getCompareFn<R>(sort: SortItemConfig<T, R>): SortFn<R> { // 类型断言确保默认比较函数符合SortFn<R>类型 return sort.compareFn ?? (compareValues as SortFn<R>); } }
内容的提问来源于stack exchange,提问作者Andrei Khotko
相关产品推荐
相关产品推荐

