javascript - Hvordan implementere et Typescript-grensesnitt i en es5-stil «klasse»?


Måten vi kan implementere et interface i en ES6-klasse på er ganske enkel:

interface IDog {
    bark(): void
}

class Dog implements IDog {
    bark(): void {

    }
}

Spørsmålet er: hvordan implementere det samme grensesnittet til denne «klassen»:

const Dog = function() {

}

Dog.prototype.bark = function() {

}

Jeg prøvde å definere typen til Dog som IDog: const Dog: IDog. Det fungerte ikke.

Så, jeg trenger det for å implementere Dependency Inversion, og jeg kan ikke finne ut hvordan jeg gjør dette med es5-klasser. Jeg så at klassisk arv-stil er et "antipattern" i Javascript, så jeg bestemte meg for å lage klasser på den gamle måten og trenger hjelp med å implementere Typescript-grensesnitt til dem.

Svar #1

Jeg antar at du ønsker en klasseimplementering i es5-stil, som er erklært for å samsvare med IDog-grensesnittet, og typekontrollert av kompilatoren for å sikre at den virkelig samsvarer med det grensesnittet.

Dårlige nyheter - TypeScript støtter ikke det. Du kan få TypeScript til å tro at es5 Dog er en klasse som implementerer IDog, men du må erklære DogConstructor-grensesnittet og bruke as any as DogConstructor-typepåstand for Dog. Og du kan ikke få TypeScript til å typekontrollere prototypebasert implementering fordi Object.prototype (og etterfølgende Dog.prototype) er erklært som any i systembiblioteket (se disse problemene for noen diskusjoner):

interface IDog {
    bark(): void
}

interface DogConstructor {
    new(): IDog;
}

const Dog = function (this: IDog) {
    // this.bark(); you can do this because of `this: IDog` annotation
} as any as DogConstructor;

Dog.prototype.bark = function() {

}

const p = new Dog();
p.bark();

Jeg tror ikke at støtte for dette noen gang vil bli forbedret. ES5-stil klasser implementeres vanligvis i JavaScript-kode som ikke er ment å typekontrolleres, og TypeScript gir nok støtte for å skrive typedeklarasjoner som tillater bruk av JavaScript-implementering på en typesikker måte. Hvis du implementerer klasser i TypeScript, kan du enkelt bruke ekte klasser.

Svar #2

Det finnes ingen språksupport for dette, det beste vi kan gjøre, hvis dette er en vanlig nok forekomst, er å rulle ut vår egen funksjon for å opprette klasser, som pålegger begrensninger på medlemmene vi legger til i klassen.

Ved å bruke noImplicitThis-kompilatoralternativet og ThisType kan vi få ganske god typekontroll for klassemedlemmer også, vi får ikke noe av det avanserte som sikker felttildeling, men det er godt nok:

interface IDog {
    bark(): void
}

function createClass<TInterfaces, TFields = {}>() {
    return function<TMemebers extends TInterfaces>(members: TMemebers & ThisType<TMemebers & TFields>) {
        return function<TCtor extends (this: TMemebers & TFields, ...a: any[]) => any>(ctor: TCtor) : FunctionToConstructor<TCtor, TMemebers & TFields> {
            Object.assign(ctor.prototype, members);
            return ctor as any;
        }
    }
}

const Dog = createClass<IDog, { age: number }>()({
    eat() {
        // this is not any and has the fields defined in the TFields parameter
        // and the methods defined in the current object literal
        for(let i =0;i< this.age;i++) {
            this.bark();
            console.log("eat")
        }
    },
    bark() {
        console.log("BA" + "R".repeat(this.age) + "K");
    }
})(function(age: number) {
    this.age = age; // this has the fields and members previously defined 
    this.bark();
})
const p = new Dog(10);
p.bark();

// Helpers
type IsValidArg<T> = T extends object ? keyof T extends never ? false : true : true;

type FunctionToConstructor<T, TReturn> =
    T extends (a: infer A, b: infer B) => void ?
    IsValidArg<B> extends true ? new (p1: A, p2: B) => TReturn :
    IsValidArg<A> extends true ? new (p1: A) => TReturn :
    new () => TReturn :
    never;

Merk Implementeringen ovenfor ligner på svaret her, og du kan lese en mer inngående forklaring av det der.

Relaterte artikler