Hvorfor klarer ikke TypeScript å utlede typen til funksjonsparametere når man implementerer et generisk grensesnitt?
Jeg skriver TypeScript i Visual Studio 2015, med versjon 2.3.3.0 av språktilleggstjenesten installert. Jeg har noImplicitAny-parameteren satt til true i prosjektets tsconfig.json.
Gitt dette enkle eksempel-koden:
interface ITransformer<TInput, TOutput> {
transform(input: TInput): TOutput;
}
class Input {
name: string;
}
class Output {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Test {
name: string;
}
class Transformer implements ITransformer<Input, Output> {
transform = (input) => new Output(input.name);
}
TS-kompilatoren gir meg en feil: TS7006: Parameter 'input' har implisitt typen 'any':
Nå kan jeg enkelt fikse denne feilen ved å legge til en typeannotasjon på input-parameteren:
class Transformer implements ITransformer<Input, Output> {
transform = (input: Input) => new Output(input.name);
}
Men spørsmålet mitt er, hvorfor skal jeg måtte det? Det virker for meg at typen til den parameteren burde bli utledet fra typen til TInput i implementeringen av grensesnittet (i dette tilfellet, Input).
Enda mer bekymringsfullt, jeg kan gjøre dette med glede:
class Transformer implements ITransformer<Input, Output> {
transform = (input: Test) => new Test();
}
Som aksepterer og returnerer en helt annen type som ikke refereres til av noen av typeparameterne, og kompilatoren ser ut til å ikke ha noe problem med det...
Kommer fra en C#-bakgrunn, dette virker bare feil. Hva går jeg glipp av?
Svar #1
Når du implementerer ITransformer-grensesnittet, kan du overstyre transform med forskjellige signaturer, for eksempel:
class Transformer implements ITransformer<Input, Output> {
transform(input: string): Output;
transform(input: number): Output;
transform(input: Input): Output;
transform(input: any): Output {
// ...
}
}
Du er ikke tvunget til å bare ha en implementering for signaturen i grensesnittet. På grunn av det må du implisitt skrive typen:
transform = (input: Input) => new Output(input.name);
Når det gjelder hvorfor dette fungerer:
transform = (input: Test) => new Test();
Det er fordi TypeScript er basert på strukturell undertyping, og de to typene har samme struktur ({ name: string }). Prøv å legge til en annen egenskap til bare én av dem, så vil du få en feil.
Det siste jeg vil påpeke er at måten du definerer klassemetoder på faktisk ikke vil opprette metoder, men bare medlemmer med en funksjonstype:
class Test {
method1() { }
method2 = () => {}
}
Her er bare method1 virkelig en metode, mens method2 bare er en egenskap som tildeles i konstruktøren, den vil ikke være en del av prototypen og du vil ikke kunne overstyre den hvis du utvider klassen.
Her er den kompilerte versjonen av denne koden:
var Test = (function () {
function Test() {
this.method2 = function () { };
}
Test.prototype.method1 = function () { };
return Test;
}());
Denne tilnærmingen er grei og blir brukt, men vær bare oppmerksom.
