Как реализовать интерфейс 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;
Примечание. Вышеуказанная реализация аналогична ответу здесь, и вы можете прочитать более подробное объяснение этого там.