Is it possible to make def
type safe, without manually defining the interfaces?
// With proper `Cat.meow(message: string)` declaration on `Cat`?
const Cat = function() {}
def(Cat, {
meow: function(message: string) { console.log('meow ' + message) },
})
const cat = new Cat()
cat.meow('I want food')
// Implementation
function def<T>(klass: T, fns: { [k: string]: any }) {
for (let k in fns) {
Object.defineProperty(klass.prototype, k, { value: fns[k],
enumerable: false, writable: true, configurable: true
})
}
}
P.S. Maybe with some Babel AST transform magic for TypeScript?
It's conceptually cleaner to just have def()
return the desired constructor instead of trying to mutate the type of its klass
argument. Still, this is possible by making it an assertion function that narrows the type of the klass
argument to one which has an appropriate construct signature.
Possibly like this:
function def<A extends any[], T extends object, M extends Record<keyof M, Function>>(
klass: (this: T, ...args: A) => void, fns: M & ThisType<T & M>
): asserts klass is typeof klass & (new (...args: A) => T & M) {
for (let k in fns) {
Object.defineProperty(klass.prototype, k, {
value: fns[k],
enumerable: false, writable: true, configurable: true
})
}
}
This accounts for klass
having some argument list of generic type A
, and tries to give a reasonably useful experience around use of this
inside the klass
and fns
properties by using a this
parameter on klass
and the magic ThisType<T>
utility type on fns
. The idea is so that whatever this
is in klass
and whatever type fns
is will be contextually available as this
inside of the argument to fns
.
So the following compiles and works as desired:
const Cat = function () { }
def(Cat, {
meow(message: string) { console.log('meow ' + message) },
})
const cat = new Cat()
cat.meow('I want food') // meow I want food
const Dog = function (this: { name: string }, name: string) {
this.name = name;
}
def(Dog, {
bark() { console.log(this.name + " barks") }
})
const dog = new Dog("Fido");
console.log(dog.name); // Fido
dog.bark(); // Fido barks