There is no language support for this, the best we can do, if this is a common enough occurrence, is to roll out our own class creating function, that imposes restrictions on the members we add to the class.
Using the noImplicitThis
compiler option and ThisType
we can get pretty good type checking for class members too, we don't get any of the fancy stuff like definite field assignment, but it is good enough:
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;
Note The above implementation is similar to the answer here and you can read a more in depth explanation of it there.