typescript - Generice: TS2345 Argumentul de tip 'T[keyof T]' nu poate fi atribuit parametrului de tip 'Date' unde


Adesea trebuie să sortezi obiecte după proprietăți, așa că am scris următorul ajutor simplu:

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

Adesea trebuie să sortezi obiecte după o proprietate de tip dată a obiectului, dar datele pe care le primesc sunt transmise ca șiruri de caractere în formatul "01-Apr-2018", așa că trebuie să le convertesc mai întâi într-un obiect Date.

Așadar, am extins cele de mai sus cu:

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

ceea ce duce la

TS2345: Argumentul de tip „T[keyof T]” nu poate fi atribuit parametrului de tip „Date”

pe a[field] și b[field].

Urmărind implementarea Date a WebStorm/serviciului TypeScript care a fost aleasă, am observat că în new Date("hello"), acesta alege următoarea interfață din 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;
}

Dar în comparatorul meu de sortare, alege interfața din lib.es2015.core.d.ts, care are doar:

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

Încercarea de a afirma cu new Date(a[field] as string) sau as Date nu are niciun efect.

Folosesc TypeScript 2.7.2 (cerut de proiectul meu curent Angular 6.0.9 și nu pot să îl schimb).

Deci :

  1. De ce alege tsc unul în locul celuilalt în aceste contexte?

  2. Cum pot să afirm care dintre ele vreau să fie folosită, sau cel puțin, să fac codul meu să se compileze în timp ce păstrez parametrul field: keyof T?

Răspuns #1

Nu pot testa pe 2.7.2 chiar acum, dar problema principală este că TypeScript nu crede că a[field] este neapărat un string. Și asta are sens pentru că a este de tip generic T, iar field este de tip keyof T, așa că tot ce știe este că a[field] este T[keyof T]. Acesta poate fi string, sau poate fi ceva pe care nu poți să aplici new Date().

Dacă știi cu siguranță că vei apela byDate doar pe ceva unde a[field] este un string, atunci poți schimba genericele astfel:

static byDate = <K extends keyof any, T extends Record<K, string>>(
  list: Array<T>, 
  field: K, 
  descending: boolean = true
) => { ... }

Asta ar trebui să facă eroarea să dispară... acum știe că a[field] este de tip T[K], unde T extinde Record<K, string>, cunoscut și sub numele de {[P in K]: string}, sau „un obiect ale cărui chei sunt în K și ale cărui valori sunt string”. Deci T[K] extinde string și este mulțumit.

De mai sus ar trebui să funcționeze pe TypeScript 2.7.2, dar anunță-mă dacă întâmpini o problemă.

Sper că asta ajută. Mult noroc!