typescript - Evitarea duplicării declarațiilor câmpurilor în definiția interfeței și a clasei


Am o interfață ca

interface I {
  a : string;
  b : number;
  c : string;
}

Și acum declar o clasă care va avea membri publici a, b, c conform interfeței I.

Așadar, declar

class C implements I {
  public a : string;
  public b : number;
  public c : string;
  // and then constructor, other fields, other methods
}

Există vreo modalitate pentru mine de a evita duplicarea la declararea câmpurilor? Adică, vreau să am acele câmpuri fără a le declara efectiv, prin simpla extindere a interfeței. Așadar, tot ce va trebui să scriu ar fi ceva de genul:

class C implements I {
  // perhaps a magic statement that asserts that C should inherit all members from I
  // and then constructor, other fields, other methods
}

Ca să fie totul și mai rău, există și mai multă duplicare în constructor deoarece nu pot pur și simplu să inițializez câmpurile dintr-un literal de obiect care satisface interfața I într-o singură linie

constructor(data : I) {
   this.a = data.a;
   this.b = data.b;
   this.c = data.c;
}

Nu am control asupra definiției interfeței, am control doar asupra definiției clasei (aceasta înseamnă că, de exemplu, nu pot transforma interfața într-o clasă abstractă...)

Răspuns #1

DA, este posibil. Necesită un cod puțin neintuitiv, dar va asigura corect că nu adaugi cod redundant.

Aveți nevoie de următoarea linie în afara definiției clasei:

interface I {
  a : string;
  b : number;
  c : string;
}

interface C extends I {}; //<--- this line 

class C implements I {
  constructor(data : I) {
    this.a = data.a;
    this.b = data.b;
    this.c = data.c;
  }
}

Link către terenul de joacă

Acum compilatorul TypeScript va considera că clasa C a declarat aceleași câmpuri ca interfața I.

Funcționează astfel:

  • interface C declară o interfață numită C.
  • `extends I` înseamnă că moștenește proprietățile din interfața existentă `I`.
  • {} este doar corpul acestei interfețe C - este gol, deoarece nu adăugăm sau schimbăm nimic.

De unul singur, nu este foarte impresionant. Totuși, există deja o clasă numită C, așadar interfața cu același nume se referă la aceeași interfață pe care o are clasa. În termeni simpli,

class A {
    public foo: string;
    constructor(data: string) {
        this.foo = data;
    }
}

compilează în JavaScript (țintă ES6+) către

class A {
    constructor(data) {
        this.foo = data;
    }
}

informațiile eliminate se referă la interfața clasei pe care TypeScript o va folosi la timpul de compilare pentru a se asigura că folosești corect clasa. De exemplu:

  • adăugarea sau referirea la proprietăți care nu sunt declarate
  • utilizând proprietățile declarate (cum ar fi foo: string) conform tipului lor (de exemplu, fără a trata foo ca pe un număr).
  • apelarea constructorului cu numărul și tipul corect de argumente

Declararea unei interfețe cu același nume ca o clasă va combina în schimb declarația cu cea a clasei. Aceasta este motivul pentru care interfața clasei poate fi manipulată. Și în acest caz, folosim extends (care este valid pentru interfețe) pentru a îmbogăți definiția clasei (unde este permis doar implements).

Ca referință, am preluat tehnica din acest comentariu într-un fir de discuție despre probleme din depozitul TypeScript pe GitHub. Titlul problemei de pe GitHub este „Declarațiile claselor ar trebui să implementeze implicit interfețele” iar comentariul este de la RyanCavanaugh din 17 februarie 2016:

Observați că în cea mai recentă versiune de TypeScript, puteți folosi combinarea de clase/interfețe pentru a face acest lucru:

interface Foo {
    a: number;
}

interface Baz extends Foo { }
class Baz {
   constructor() {
       console.log(this.a); // no error here
   }
}

mulțumiri @jeffreymorlan pentru că mi-a reamintit de asta

[Notă: cea mai recentă versiune lansată la momentul comentariului ar fi fost 1.8 Beta]