Reputation: 858
I know it's too generic, but I wish to make a class that would have all props and prototype from a generic type like this:
class GenericExtend<T> extends T {
constructor(data: T) {
// here is a workaround to make these 2 classes unique
const proto = { ...GenericExtend.prototype };
Object.assign(proto, Object.getPrototypeOf(data));
Object.setPrototypeOf(this, proto);
Object.assign(this, data);
}
GenericMethod() { }
}
And now on, I could instanciate GenericExtend
class and then get the types of the two classes like this:
const obj = new GenericExtend<Model>(data);
obj.GenericMethod(); // ok
obj.ModelMethod(); // good!
One of my solution is to use intersection, something like this:
const obj: GenericExtend & Model = new GenericExtend(data) as any;
It worked, but I didn't like that much. Is there something better that I can do?
Upvotes: 14
Views: 20042
Reputation: 5913
I came accross into the similar requirement and finally made it work in the following way, by which no intersection is strictly needed (you can define a dedicated type for type checking), hope it helps.
class A {
a() {
...
}
}
class B {
b() {
...
}
}
type Constructor = new (...args: any[]) => any
function genericExtend<T extends Constructor>(target: T) {
return class GenericExtended extends target {
constructor(...args: any[]) {
super(...args)
}
genericMethod() {
...
}
}
}
const instanceOfA: GenericExtended & A = new (genericExtend(A))()
const instanceOfB = new (genericExtend(B))()
instanceOfA.a() // ok with type checking
instanceOfA.genericMethod() // ok with type checking
instanceOfB.b() // ok without type checking
instanceOfB.genericMethod() // ok without type checking
Upvotes: 2
Reputation: 330086
TypeScript will not let you implement or extend another type T
unless all the keys of T
are statically known at compile time. That prevents class GenericExtend<T> implements T {...}
from being something you can write.
Instead, you have to use an intersection to get this behavior... but you can confine the type assertion to the constructor function, if you want, so that subsequent uses will not require it. Let's rename GenericExtend
out of the way:
class _GenericExtend<T> {
constructor(data: T) {
const proto = { ..._GenericExtend.prototype };
Object.assign(proto, Object.getPrototypeOf(data));
Object.setPrototypeOf(this, proto);
Object.assign(this, data);
}
GenericMethod() { }
}
And then redefine GenericExtend
as both a type and a constructor with the intersection behavior you want:
type GenericExtend<T> = _GenericExtend<T> & T;
const GenericExtend: new <T>(data: T) => GenericExtend<T> = _GenericExtend as any;
That last as any
is the type assertion we need. Now you should be able to get the behavior you want:
interface Model {
ModelMethod(): void;
}
declare const data: Model;
const obj = new GenericExtend(data);
obj.GenericMethod(); // ok
obj.ModelMethod(); // ok
Upvotes: 17