ecmascript 6 - Utvide type når man utvider en ES6-klasse med en TypeScript-dekoratør


Jeg prøver å dekorere en klasse med en dekoratør (a-la-angular-stil), og legge til metoder og egenskaper til den.

dette er min eksempel på en dekorert klasse:

@decorator
class Person{

}

og dette er dekoratøren:

const decorator = (target)=>{
    return class New_Class extends target {
        myProp:string
    }
}

men myProp er ikke en kjent egenskap ved Person:

person.myProp //Error - myProp does not exist on type Person

Hvordan kan jeg dekorere en TypeScript-klasse og bevare typekomplettering, typesikkerhet, osv?

Svar #1

Jeg fant en løsning for å implementere en slags flere arver (egentlig kaskaderer det), men det er verdt å ta en titt.

Anta at du har en klasse Base med noen egenskaper og metoder:

class Base {
    tableName = 'My table name';
    hello(name) {
     return `hello ${name}`;
    }
}

Og du vil at en klasse skal utvide Base, men du har også definert noen egenskaper du vil gjenbruke. For det vil du gjøre følgende funksjon:

type Constructor<T = {}> = new (...args: any[]) => T;
function UserFields<TBase extends Constructor>(Base: TBase) {
    return class extends Base {
        name: string;
        email: string;
    };
}

Nå kan vi lage en klasse som utvider Base og utvider UserFields, og TypeScript-språkservice vil finne egenskaper fra begge klassene. Det etterligner en form for flere arv, men det er egentlig en kaskade.

class User extends UserFields(Base) { }
const u = new User();
u.tableName = 'users'; // ok
u.name = 'John'; // ok

På denne måten kan du gjenbruke UserFields-funksjonen med andre klasser. Et klart eksempel på dette er hvis du ønsker å eksponere User-objektet på klientsiden som et «rent» objekt og ha feltene tilgjengelige, og så har du et UserDb-objekt med tilkoblinger til databasen og andre server-side metoder kan ha de samme feltene også. Vi definerer databasens feltene bare én gang!

En annen god ting er at du kan kjede mixins mixin1(mixin2....(Base)) for å ha så mange attributter du vil ha i den samme klassen.

Alle forventer at dekoratorattributtene skal være synlige for TypeScript, men i mellomtiden er det en god løsning.

Svar #2

Det finnes en GitHub-issue om dette med mye diskusjon. Jeg tror sammendraget er: dekoratører endrer ikke typen til en klasse (det meste av diskusjonen handler om hvorvidt det burde eller ikke burde være slik) og derfor kan du ikke gjøre det på den måten du vil, slik:

const decorator = (target: new (...args: any) => any) => {
  // note I'm extending target, not Person, otherwise you're not
  // decorating the passed-in thing
  return class New_Class extends target {
    myProp!: string
  }
}

@decorator
class Person {
  noKnowledgeOfMyProp: this['myProp'] = "oops"; // error
}

declare const p: Person;
p.myProp; // error, uh oh

Hva du kunne gjøre i stedet er å bare bruke dekoratøren din som en vanlig mixin-funksjon, og la Person utvide returverdien av den. Du ender opp med to klassedefinisjoner... en som blir sendt inn i decorator og en som den nye klassen din utvider. Den "indre" klassen (sendt til decorator()) har fortsatt ingen kunnskap om de added egenskapene, men den "ytre" klassen gjør det:

class Person extends decorator(class {
  innerProp: string = "inner";
  noKnowledgeOfMyProp: this['myProp'] = "oops"; // error
}) {
  outerProp: string = "outer"
  hasKnowledgeOrMyProp: this['myProp'] = "okay"; // okay
}

declare const p: Person;
p.myProp; // okay

Hjelper det? Lykke til!

Relaterte artikler