Как реализовать интерфейс Typescript для "класса" в стиле es5?

Способ, которым мы можем реализовать интерфейс к классу es6, довольно прост:

interface IDog {
    bark(): void
}

class Dog implements IDog {
    bark(): void {

    }
}

Вопрос в том, как реализовать тот же интерфейс для этого "класса":

const Dog = function() {

}

Dog.prototype.bark = function() {

}

Я попытался определить тип собаки как IDog: const Dog: IDog, Не сработало

Итак, мне нужно это для реализации Dependency Inversion, и я не могу понять, как это сделать с классами es5. Я увидел, что стиль классического наследования является "антипаттерном" в Javascript, поэтому я решил создать классы по-старому, и мне нужна помощь в реализации интерфейсов Typescript.

2 ответа

Решение

Я предполагаю, что вы хотите реализацию класса в стиле es5, которая объявлена ​​соответствующей IDog интерфейс, и проверяется тип компилятором, чтобы убедиться, что он действительно соответствует этому интерфейсу.

Плохие новости - TypeScript не поддерживает это. Вы можете заставить TypeScript думать, что es5 Dog это класс, который реализует IDog, но вы должны объявить DogConstructor интерфейс и использование as any as DogConstructor введите утверждение для Dog, И вы не можете заставить TypeScript проверять реализацию на основе прототипов, потому что Object.prototype (и впоследствии Dog.prototype) объявляется как any в системной библиотеке (см. эти вопросы для обсуждения):

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();

Я не думаю, что поддержка этого когда-либо будет улучшена. Классы в стиле Es5 обычно реализуются в коде javascript, который не предназначен для проверки типов, а TypeScript обеспечивает достаточную поддержку для написания объявлений типов, которые позволяют использовать реализацию javascript безопасным для типов способом. Если вы реализуете классы в TypeScript, вы можете просто использовать реальные классы.

Для этого нет языковой поддержки, лучшее, что мы можем сделать, если это достаточно распространенное явление, - это развернуть нашу собственную функцию создания класса, которая накладывает ограничения на члены, которые мы добавляем в класс.

С использованием noImplicitThis опция компилятора и ThisType мы можем получить довольно хорошую проверку типов для членов класса, мы не получим ничего такого, как определенное назначение поля, но это достаточно хорошо:

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;

Примечание. Вышеуказанная реализация аналогична ответу здесь, и вы можете прочитать более подробное объяснение этого там.

Другие вопросы по тегам