TypeScript对象字段泛型比较函数的泛型场景报错问题
解决TypeScript泛型
compareFields函数的类型兼容问题 问题背景
我编写了一个泛型compareFields函数,用于生成可根据指定字段对对象排序的比较函数,要求指定字段的类型能通过内置比较运算符安全比较。以下是原类型定义与函数实现:
// T's fields that extend U. // - Example: FieldsThatExtend<{a: string, b: number, c: string}> === 'a' | 'c' type FieldsThatExtend<T, U> = {[K in keyof T]: T[K] extends U ? K : never}[keyof T]; // T's fields that can be compared with the built-in comparison operators // - Example: DirectlyComparableFields<{a: string, b: number, c: string | number}> === 'a' | 'b' export type DirectlyComparableFields<T> = | FieldsThatExtend<T, string> | FieldsThatExtend<T, number> | FieldsThatExtend<T, Date> | FieldsThatExtend<T, boolean> | FieldsThatExtend<T, bigint>; type CompareFn<T> = (a: T, b: T) => number; export function compareFields<T>(...fields: Array<DirectlyComparableFields<T>>): CompareFn<T> { return (a, b) => { for (const field of fields) { const fa = a[field]; const fb = b[field]; if (fa > fb) return 1; if (fa < fb) return -1; } return 0; }; }
正常工作场景
该函数在多数场景下表现符合预期:
// This works type PlainRow = {a: string, b: number, c: number | string, d: string[], e: {}}; function f1(a: Array<PlainRow>): void { a.sort(compareFields('a')) // Works a.sort(compareFields('b')) // Works a.sort(compareFields('c')) // Error, as expected. a.sort(compareFields('d')) // Error, as expected. a.sort(compareFields('e')) // Error, as expected. } // This also works type GenericRow<T> = {a: T, b: number, c: number | string, d: string[], e: {}}; function f2(a: Array<GenericRow<string>>): void { a.sort(compareFields('a')) // Works a.sort(compareFields('b')) // Works a.sort(compareFields('c')) // Error, as expected. a.sort(compareFields('d')) // Error, as expected. a.sort(compareFields('e')) // Error, as expected. }
意外报错场景
但在泛型参数定义在函数上、或存在类型交叉的场景中,出现了意外报错:
// Doesn't work when the type parameter is on the function. function f3<T extends string>(a: Array<GenericRow<T>>): void { a.sort(compareFields('a')) // <---------- UNEXPECTED ERROR! a.sort(compareFields('b')) // Works a.sort(compareFields('c')) // Error, as expected. a.sort(compareFields('d')) // Error, as expected. a.sort(compareFields('e')) // Error, as expected. } // Doesn't work when intersecting a new field. function f4<T>( sortFields: Array<DirectlyComparableFields<T>>, a1: Array<T>, a2: Array<T & {extraField: string}>, ): void { a1.sort(compareFields(...sortFields)); // Works a2.sort(compareFields(...sortFields)); // <---------- UNEXPECTED ERROR! }
解决方案
问题根源在于TypeScript对泛型子类型和交叉类型的类型推断限制,我们需要调整类型定义和函数泛型约束,增强兼容性:
修改后的代码
// 调整条件判断逻辑,兼容泛型子类型与交叉类型 type FieldsThatExtend<T, U> = { [K in keyof T]: U extends T[K] ? K : never }[keyof T]; export type DirectlyComparableFields<T> = | FieldsThatExtend<T, string> | FieldsThatExtend<T, number> | FieldsThatExtend<T, Date> | FieldsThatExtend<T, boolean> | FieldsThatExtend<T, bigint>; type CompareFn<T> = (a: T, b: T) => number; // 拆分泛型参数,增强类型推断准确性 export function compareFields<T, K extends DirectlyComparableFields<T>>( ...fields: K[] ): CompareFn<T> { return (a, b) => { for (const field of fields) { const fa = a[field]; const fb = b[field]; if (fa > fb) return 1; if (fa < fb) return -1; } return 0; }; }
关键修改点
- 调整
FieldsThatExtend的条件判断:将T[K] extends U改为U extends T[K],这样当T[K]是U的子类型(比如泛型T extends string的场景),也能正确匹配字段类型,解决泛型参数在函数上的报错。 - 拆分函数泛型参数:把
compareFields的泛型拆分为T和K extends DirectlyComparableFields<T>,增强类型推断的准确性,同时让函数更好地处理交叉类型场景。
修改后验证
修改后的代码在之前报错的场景中均可正常工作:
// 现在正常工作 function f3<T extends string>(a: Array<GenericRow<T>>): void { a.sort(compareFields('a')) // 不再报错 a.sort(compareFields('b')) // Works a.sort(compareFields('c')) // Error, as expected. a.sort(compareFields('d')) // Error, as expected. a.sort(compareFields('e')) // Error, as expected. } // 现在正常工作 function f4<T>( sortFields: Array<DirectlyComparableFields<T>>, a1: Array<T>, a2: Array<T & {extraField: string}>, ): void { a1.sort(compareFields(...sortFields)); // Works a2.sort(compareFields(...sortFields)); // 不再报错 }
内容的提问来源于stack exchange,提问作者Kannan Goundan
相关产品推荐
相关产品推荐

