Hvordan referere til typen av self i Typescript-grensesnitt (for et IClonable-grensesnitt)
Jeg trenger et IClonable-grensesnitt som definerer et clone()-medlem, som returnerer en instans av klassen som implementerte det.
Hvis mulig, hvordan kan jeg indikere at returtypen til clone() vil være den samme som klassen den kalles på?
interface IClonable {
clone(): ???
}
Jeg vet at jeg kan gjøre dette med generiske typer som nedenfor, men det virker altfor omstendelig
interface IClonable<T> {
clone(): T
}
Svar #1
Oppdatering 11. oktober 2022
Det forrige svaret hadde en feil som bare viser seg når arvekjeden vokser utover vårt enkle eksempel. Vurder dette eksemplet:
class B extends A {
foo() {
console.log("Foobar");
}
}
const b = new B(3).clone() // type: B ???
b.foo(); // RuntimeError: b.foo is not a function
Lekeplasslenke
Det er et problem her som ikke vil bli oppdaget ved kompilering, og først under kjøring vil vi se det. Derfor foreslår jeg enda et annet alternativ:
Løsning
interface ICloneable {
clone(): ThisType<this>;
}
class A implements ICloneable {
constructor(readonly a: number){}
clone() {
return new A(this.a);
}
}
Bruk:
const a = new A(42).clone(); // type: A
class B extends A {
foo() {
console.log("Foobar");
}
}
const b = new B(3).clone() // type: A
// @ts-expect-error
b.foo();
Ved å bruke en kontekstualisert this-type, er returtypen for clone bundet til konteksten hvor den ble erklært, og ikke konteksten hvor den ble kalt.
Nå, hvis vi fjerner // @ts-expect-error-kommentaren, vil TypeScript oppdage denne feilen under kjøring, fordi klonemetoden vi har i B ikke returnerer typen B.
Forrige svar:
Bruk (polymorf) this som returtype:
interface ICloneable {
clone(): this;
}
I klasser refererer en spesiell type kalt
thisdynamisk til typen til den nåværende klassen.
Bruk:
class A implements ICloneable {
constructor(readonly a: number){}
clone() {
return new A(this.a) as this;
}
}
Svar #2
Du har rett, å bruke generiske typer er måten å gjøre det på, selv om det er omstendelig..
Du kan også:
interface IClonable {
clone(): any;
}
eller
interface IClonable {
clone(): any;
clone<T>(): T;
}
eller
interface IClonable {
clone(): IClonable;
}
Men å bruke generiske typer er sannsynligvis den beste måten å gå på.
Rediger
@Silvermind kommentarer nedenfor fikk meg til å sjekke den foreslåtte koden for clone<T>(): T og jeg tok feil siden den ikke kompilerer.
Først her er en kode for å vise hva jeg mente:
class Point implements IClonable {
private x: number;
private y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
public clone<T>(): T {
return <T> new Point(this.x, this.y);
}
}
var p1 = new Point(1, 2);
var p3 = p1.clone<Point>();
I utgangspunktet å utføre casten i clone-metoden i stedet for å caste den returnerte verdien.
Men <T> new Point(this.x, this.y) gir:
Hverken typen 'Point' eller typen 'T' kan tildeles hverandre
