You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
    };
}

关键修改点

  1. 调整FieldsThatExtend的条件判断:将T[K] extends U改为U extends T[K],这样当T[K]是U的子类型(比如泛型T extends string的场景),也能正确匹配字段类型,解决泛型参数在函数上的报错。
  2. 拆分函数泛型参数:把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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 23:14:51