I have the following code snippet which i find it quite hard to understand:
export class Record{
};
export class RecordMissingExtendsError{
constructor(r:any){
}
}
export function Model() {
return <T extends { new(...args: any[]): {} }>(ctr: T) => {
if (!(ctr.prototype instanceof Record)) {
throw new RecordMissingExtendsError(ctr);
}
return (class extends ctr {
constructor(...args: any[]) {
const [data] = args;
if (data instanceof ctr) {
return data;
}
super(...args);
(this as any)._completeInitialization();
}
});
};
}
I am having difficult time making sense of above code and understood as much as the following:
Model returns type T (I know what generics are so don't worry about explaining generics) in which
T extends { new(...args: any[]): {}
What does above mean? T going to keep existing properties plus extra added features?
In addition can you explain the function return type? Are we adding an extra constructor to T?
(class extends ctr {
constructor(...args: any[]) {
const [data] = args;
if (data instanceof ctr) {
return data;
}
super(...args);
(this as any)._completeInitialization();
}
});
T extends { new(...args: any[]): {} }
means thatT
must be a constructor function (ie a class). The constructor arguments as well as return type don't matter (T
can have any number of arguments and may return any type that extends{}
, in effect any object type).If invoked directly the
T
will be the class. The approach being used to type this decorator is basically that of mixins (described for typescript here).The return value of the function will be a new class that inherits the decorated class. As such it's not adding a constructor but rather replacing the original constructor with a new one, and calling the original constructor through the
super
call.The generics are a bit of overkill in this situation in my opinion. They are useful for mixins, because they forward the original class from input parameter to output parameter (and the mixin adds members to the type). But since decorators can't change the structure of the type, there is nothing to forward. Also instead of the constructor returning
{}
I would type it to returnRecord
since you check that at runtime time, might as well check it at compile time as well: