typescript - Generics: TS2345 Argument av typen 'T[keyof T]' kan ikke tildeles parameteren av typen 'Date' hvor


Jeg må ofte sortere objekter etter egenskaper, så jeg skrev følgende enkle hjelpefunksjon:

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

Jeg må ofte sortere objekter etter en datolignende egenskap på objektet, men datoene jeg mottar er gjort om til strenger i formatet "01-Apr-2018", så jeg må først konvertere dem til en dato.

Så, jeg utvidet det ovenstående med:

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

som resulterer i

TS2345: Argument av type 'T[keyof T]' kan ikke tildeles parameter av type 'Date'

på a[field] og b[field].

Når jeg sporet WebStorms/TypeScript-tjenestes implementering av Date som ble valgt, la jeg merke til at i new Date("hello"), velger den følgende grensesnitt fra 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;
}

Men i min sorteringskomparator velger den grensesnittet fra lib.es2015.core.d.ts, som bare har:

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

Forsøk på å påstå med new Date(a[field] as string) eller as Date har ingen effekt.

Jeg bruker TypeScript 2.7.2 (påkrevd av mitt nåværende Angular 6.0.9-prosjekt og kan ikke endres).

Så :

  1. Hvorfor velger tsc den ene fremfor den andre i disse kontekstene?

  2. Hvordan kan jeg fastslå hvilken jeg ønsker at den skal bruke, eller i det minste få koden min til å kompilere samtidig som jeg beholder field: keyof T-parameteren?

Svar #1

Jeg kan ikke teste på 2.7.2 akkurat nå, men hovedproblemet er at TypeScript ikke tror at a[field] nødvendigvis er en string. Og det gir mening fordi a er av generisk type T, og field er av typen keyof T, så alt den vet er at a[field] er T[keyof T]. Det kan være string, eller det kan være noe som du ikke kan kalle new Date() på.

Hvis du vet sikkert at du bare vil kalle byDate på noe der a[field] er en string, kan du endre generics slik:

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

Det burde få feilen til å forsvinne... den vet nå at a[field] er typen T[K], hvor T utvider Record<K, string>, også kjent som {[P in K]: string}, eller «et objekt hvis nøkler er i K og hvis verdier er string». Så T[K] utvider string og det er fornøyd.

Ovenstående bør fungere på TypeScript 2.7.2, men gi meg beskjed hvis du støter på et problem.

Håper det hjelper. Lykke til!

Relaterte artikler