Metode dinamice ale claselor în Typescript


Problemă

Cum poate cineva să adauge verificarea tipului la o metodă a unei clase create dinamic?

Exemplu

Având o clasă Property foarte simplă.

class Property {
  value: any;
  name: string;

  constructor(name: string, value: any) {
    this.name = name;
    this.value = value
  }
}

și o clasă Entity

class Entity {
  name: string;
  properties: Property[];


  constructor(name: string, properties: Property[]) {
    this.name = name;
    this.properties = properties;

    this.properties.forEach((p: Property, index: number) => {
      this[p.name] = (value: string): any => {
        if (value) {
          this.properties[index].value = value;
        }
        return this.properties[index].value;
      }
    }, this);
  }
}

Parte importantă: this[p.name]=function ... (nu știm numele metodei în momentul „transpile”).

Primim următoarea eroare atunci când transpilăm în JavaScript:

var car = new domain.Entity(
  'car',
  [
    new domain.Property('manufacturer', 'Ford'),
    new domain.Property('model', 'Focus')
  ]
);

car.model() // error TS2339: Property 'model' does not exist on type 'Entity'.

Știu că aceasta este o utilizare neobișnuită a claselor, deoarece diferite instanțe ale Entity vor avea metode diferite definite. Există vreo modalitate de a scăpa de eroare, adică TypeScript să poată identifica interfața corectă pentru fiecare instanță, sau cel puțin să se suprime eroarea?

Note

Acesta este javascript valid și cineva l-ar putea folosi în următorul mod:

var car = new domain.Entity(
  'car',
  [
    new domain.Property('manufacturer', 'Ford'),
    new domain.Property('model', 'Focus')
  ]
);

car.model()          // 'Focus'
car.model('Transit') // 'Transit'

Sunt conștient că acest lucru a fost întrebat într-o întrebare asemănătoare, dar acest caz este puțin diferit, deoarece numele metodei este de asemenea definit la rulare.

Răspuns #1

Adaugă acest tip (doar o dată necesar):

interface Prop<T> {
    (): T;
    (value: T): T;
}

atunci poți să scrii asta pentru fiecare formă pe care o creezi:

interface Car extends Entity {
    model: Prop<string>;
    manufacturer: Prop<string>;
}
let car = <Car>new Entity('car', [/*...*/]);
car.model(32); // Error
let x = car.model(); // x: string

Răspunsul #2

Dacă ai proprietăți dinamice la care vrei să accesezi, atunci folosește tipul any pentru a ocoli verificările de tip pentru variabilă. Poți să declari variabila cu tipul any de la început, sau să folosești operatorul de aserțiune a tipului (as) mai târziu. Așadar, iată câteva variante posibile:

var car: any = new domain.Entity(...);
car.model();

var car = new domain.Entity(...) as any;
car.model();

var car = new domain.Entity(...);
(car as any).model();