泛型:TS2345 类型 'T[keyof T]' 的参数不能赋值给类型 'Date',其中 <T extends {}>。

5

我经常需要按照属性对对象进行排序,因此我编写了以下简单的帮助程序:

static sort = <T extends {}>(list: Array<T>, field: keyof T, descending: boolean = true) =>
  list.sort((a, b) => {
    if (a[field] > b[field]) {
      return descending ? -1 : 1;
    }
    if (a[field] < b[field]) {
      return descending ? -1 : 1;
    }
    return 0;
  });

我经常需要按照对象上的类似日期的属性对对象进行排序,但我收到的日期是以字符串形式呈现,格式为"01-Apr-2018",因此我需要先将它们转换为日期。
因此,我扩展了上面的内容:
static byDate = <T extends {}>(list: Array<T>, field: keyof T, descending: boolean = true) =>
  list.sort((a, b) => {
    if (new Date(a[field]).getTime() > new Date(b[field]).getTime()) {
      return descending ? -1 : 1;
    }
    if (a[field] < b[field]) {
      return descending ? 1 : -1;
    }
    return 0;
  });

这导致了以下错误:

TS2345: 类型“T[keyof T]”的参数不能赋给类型“Date”的参数。

出现在 a[field]b[field] 上。

跟踪 WebStorm / TypeScript Service 对所选择的 Date 实现,我注意到在 new Date("hello") 中,它从 lib.es5.d.ts 中选择了以下接口:

interface DateConstructor {
    new(): Date;
    new(value: number): Date;
    new(value: string): Date; // THIS ONE (which I expect)
    new(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): Date;
    (): string;
    readonly prototype: Date;
    parse(s: string): number;
    UTC(year: number, month: number, date?: number, hours?: number, minutes?: number, seconds?: number, ms?: number): number;
    now(): number;
}

然而在我的排序比较器中,它选择了从 lib.es2015.core.d.ts 中只包含以下内容的接口:

interface DateConstructor {
    new (value: Date): Date;
}

尝试使用new Date(a[field] as string)as Date进行断言没有效果。
我正在使用TypeScript 2.7.2版本(由我的当前Angular 6.0.9项目所需,无法更改)。
所以:
1.在这些上下文中,为什么tsc选择其中之一?
2.如何断言我想要使用哪一个,或者至少在保持field:keyof T参数的同时让我的代码编译?
1个回答

10
我现在无法在2.7.2上进行测试,但主要问题是TypeScript不认为a[field]一定是一个字符串。这是有道理的,因为a是泛型类型T,而field是类型为keyof T的类型,所以它只知道a[field]是T[keyof T]。那可能是字符串,也可能是你无法调用new Date()的东西。
如果您确定只会在a[field]为字符串的情况下调用byDate,则可以将泛型更改为以下内容:
static byDate = <K extends keyof any, T extends Record<K, string>>(
  list: Array<T>, 
  field: K, 
  descending: boolean = true
) => { ... }

这应该可以解决错误...现在它知道a[field]的类型是T[K],其中T继承自Record<K, string>,也就是 {[P in K]: string},或者说是“键在K中且值为string的对象”。因此,T[K]继承了string类型,现在没有问题啦。

上述内容在 TypeScript 2.7.2 上应该可以正常工作,但如果遇到问题,请告诉我。

希望对你有所帮助,祝好运!


1
确实如此!那正是我正在寻找的通用类型。非常感谢您提供的额外解释。 - msanford

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接