Typescript-feil: Konteksten 'this' av typen ... kan ikke tilordnes metodens 'this' av typen


Jeg feilsøker for øyeblikket koden til noen biblioteker og for livet forstår jeg ikke en spesifikk feil.

Her er et forenklet eksempel på koden jeg feilsøker:

type Test1Type<V> = object | ((this: V) => object);

function testFnc(x: string, test1Fnc: Test1Type<string>) {
  let res;

  if (typeof test1Fnc === "function") {
    res = test1Fnc.call(x, x);
  }
}

I linjen res=test1Fnc.call(x, x); er begrepet test1Fnc markert som en feil:

(parameter) test1Fnc: Function | ((this: string) => object)

The 'this' context of type 'Function | ((this: string) => object)' is not assignable to method's 'this' of type '((this: string) => object) & Function'.
  Type 'Function' is not assignable to type '((this: string) => object) & Function'.
    Type 'Function' is not assignable to type '(this: string) => object'.
      Type 'Function' provides no match for the signature '(this: string): object'.ts(2684)

Den interne typedefinisjonen av .call() ser ut til å være denne:

call<T, A extends any[], R>(this: (this: T, ...args: A) => R, thisArg: T, ...args: A): R;

Dessverre klarer jeg bare ikke å finne ut hva som er galt med denne koden, spesielt siden biblioteket jeg feilsøker har dette direkte i sin kodebase (se).

Jeg forstår egentlig ikke hva feilmeldingen prøver å fortelle meg her.

Jeg bruker "typescript": "~3.9.3" (biblioteket bruker "typescript": "^2.8.3").

Enhver hjelp settes pris på, siden jeg virkelig ønsker å forstå hva som foregår her.

Svar #1

Du har funnet en inkonsekvens i måten TypeScript håndterer interaksjonen mellom Function og unionstyper.

Hva kompilatoren gjør

Når call kalles, vet kompilatoren at test1Fnc enten er:

  1. et JavaScript-Function-objekt med en ukjent signatur, eller
  2. et JavaScript-Function-objekt som kalles på en streng (dvs. det har en streng som sitt this-argument) og returnerer et objekt.

Kompilatoren spør deretter om kallet er gyldig for hver av disse typene.

Når det spør om kallet er gyldig for type 1, konkluderer det med at kallet ikke er det fordi test1Fnc kanskje ikke kan kalles på en streng.

Hvorfor dette er merkelig

Dette er merkelig fordi dette er i strid med vanlig TypeScript-opptreden for Function-objekter, hvor det normalt tillater alle slags funksjonskall. For å se at slike kall er tillatt, kan du midlertidig endre den opprinnelige typedefinisjonen til type Test1Type<V>=object, og feilen forsvinner.

Dette skjer fordi to ting om hvordan Function-typer behandles av kompilatoren, er sanne:

  1. Enhver form for kall av en funksjon på dem er tillatt, men
  2. De kan ikke tildeles til en funksjon med spesifiserte argumenter.

Når kompilatoren vet at test1Fnc kun er en Function (som når typedeklarasjonen endres), fungerer det på grunn av regel 1. Men når unionstypen er involvert, prøver den først å tilordne unionstypen til den gitte signaturen til call-typen. Den tilordningen mislykkes på grunn av regel 2 før regel 1 har hatt en sjanse til å overstyre den.

Hvorfor dette ikke er hvordan kompilatoren bør oppføre seg

Du kan se at dette ikke er ønsket oppførsel fordi hvis Test1Type<V> er definert til å være bare en av de to typene (forutsatt at endringen forklart i neste seksjon er gjort), så gir ikke kompilatoren noen feil. Dette er ikke riktig: hvis en vei gjennom programmet er gyldig for en av typene i en unionstype, skal programmet være gyldig med unionstypen.

Likevel er typen feil uansett

Som en annen observasjon, selv om test1Fnc er type 2 fra ovenfor, ville den ikke være anropbar med call-funksjonen som er angitt. Den gitte call-funksjonen forventer en this-verdi av typen string og også ett vanlig argument av typen string, mens type 2 fra ovenfor bare er en this-verdi av typen string uten vanlige argumenter. Kompilatoren viser ikke denne feilen for øyeblikket, men hvis du endrer den opprinnelige typdeklarasjonen til type Test1Type<V>=((this: V) => object), vil du se en annen feil som blir produsert. Dette elimineres igjen ved å endre linjen til type Test1Type<V>=((this: V, arg: V) => object).

Forslag: redefiner, kast, rapporter og ikke bruk unioner

For å håndtere dette, kan du vurdere ett eller flere av følgende:

  1. Omskriv Test1Type<V>=object | ((this: V, arg: V) => object), som ser ut til å være det koden forventer. Dette endrer egentlig ikke typen siden det fortsatt kan være et hvilket som helst objekt, men det uttrykker kodens intensjon bedre
  2. For å fjerne feilen, hvis du kan kaste typen til test1Fnc innenfor if-blokken til den forventede typen: res=(test1Fnc as (this: string, arg: string) => object).call(x,x). Hvis du ikke klarer å gjøre en slik endring, må du kanskje gi opp all typesikkerhet og gå for type Test1Type=object.
  3. Du kan ha lyst til å rapportere dette som et problem på TypeScript GitHub. Jeg er ikke sikker på om det er en prioritet for dem, ettersom bruk av Function-objekter til typifisering sannsynligvis ikke oppmuntres, og
  4. Jeg vil uansett fraråde bruk av denne unionstypen. Den er så generell at den egentlig ikke oppnår typesikkerhet uansett. Det er ikke klart hvor mye kontroll du har over koden, og hvilke andre verdier Test1Type<V> kan trenge å være, men hvis du kan gjøre bredere endringer, vil det være å foretrekke å skrive den som en union av mer eksplisitte typer slik at kompilatoren kan velge mellom dem, eller sette opp en klassehierarki mer grundig i full OOP-stil.

Relaterte artikler