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 this dynamisk 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

Relaterte artikler