admin管理员组文章数量:1289911
I have a Base generic class:
abstract class BaseClass<T> {
abstract itemArray: Array<T>;
static getName(): string {
throw new Error(`BaseClass - 'getName' was not overridden!`);
}
internalLogic() {}
}
and inheritors:
type Item1 = {
name: string
}
class Child1 extends BaseClass<Item1> {
itemArray: Array<Item1> = [];
static getName(): string {
return "Child1";
}
}
type Item2 = {
name: number
}
class Child2 extends BaseClass<Item2> {
itemArray: Array<Item2> = [];
static getName(): string {
return "Child2";
}
}
Now I want to have to define an object with the inheritors as its values:
type IChildrenObj = {
[key: string]: InstanceType<typeof BaseClass>;
};
/*
The following error is received: Type 'typeof BaseClass' does not satisfy the constraint 'new (...args: any) => any'.
Cannot assign an abstract constructor type to a non-abstract constructor type. ts(2344)
*/
const Children: IChildrenObj = {
C1: Child1,
C2: Child2,
}
Lastly, I want to be able to use statics methods of the children, and also be able to create instances of them:
const child: typeof BaseClass = Children.C1;
/*
received the following error: Property 'prototype' is missing in type '{ getName: () => string; }' but required in type 'typeof BaseClass'. ts(2741)
*/
console.log(child.getName());
const childInstance: BaseClass = new child();
/*
The following 2 errors are received:
(1) Generic type 'BaseClass<T>' requires 1 type argument(s). ts(2314)
(2) Cannot create an instance of an abstract class. ts(2511)
Generic type 'BaseClass<T>' requires 1 type argument(s). ts(2314)
*/
I have a Base generic class:
abstract class BaseClass<T> {
abstract itemArray: Array<T>;
static getName(): string {
throw new Error(`BaseClass - 'getName' was not overridden!`);
}
internalLogic() {}
}
and inheritors:
type Item1 = {
name: string
}
class Child1 extends BaseClass<Item1> {
itemArray: Array<Item1> = [];
static getName(): string {
return "Child1";
}
}
type Item2 = {
name: number
}
class Child2 extends BaseClass<Item2> {
itemArray: Array<Item2> = [];
static getName(): string {
return "Child2";
}
}
Now I want to have to define an object with the inheritors as its values:
type IChildrenObj = {
[key: string]: InstanceType<typeof BaseClass>;
};
/*
The following error is received: Type 'typeof BaseClass' does not satisfy the constraint 'new (...args: any) => any'.
Cannot assign an abstract constructor type to a non-abstract constructor type. ts(2344)
*/
const Children: IChildrenObj = {
C1: Child1,
C2: Child2,
}
Lastly, I want to be able to use statics methods of the children, and also be able to create instances of them:
const child: typeof BaseClass = Children.C1;
/*
received the following error: Property 'prototype' is missing in type '{ getName: () => string; }' but required in type 'typeof BaseClass'. ts(2741)
*/
console.log(child.getName());
const childInstance: BaseClass = new child();
/*
The following 2 errors are received:
(1) Generic type 'BaseClass<T>' requires 1 type argument(s). ts(2314)
(2) Cannot create an instance of an abstract class. ts(2511)
Generic type 'BaseClass<T>' requires 1 type argument(s). ts(2314)
*/
Share
Improve this question
edited May 16, 2021 at 15:38
A-S
asked May 16, 2021 at 15:26
A-SA-S
3,1533 gold badges31 silver badges44 bronze badges
17
- 1 Please share Offer class/type – captain-yossarian from Ukraine Commented May 16, 2021 at 15:34
- 1 Do you want values to be instances of BaseClass or classes? – captain-yossarian from Ukraine Commented May 16, 2021 at 15:40
-
1
If you want class constructors to be values, then you don't want
InstanceType
. The typeIChildrenObj
is not going to be useful the way you want; instead it seems you just want to constrainChildren
to something that has subclass constructors in it, in which case you'd be better off with a constrained helper function like this. The same with annotatingchild1
withtypeof BaseClass
(inappropriate) orchildInstance
asBaseClass
(not valid type). Can you elaborate on why you need these types at all? Why not just use type inference as in the linked code? – jcalz Commented May 16, 2021 at 16:48 - 1 Maybe this article will be helpfull for you catchts./oop-style – captain-yossarian from Ukraine Commented May 16, 2021 at 17:27
-
1
@A-S I think @captain-yossarian wrote that article, but credited me with some piece of code in it. Anyway, does this work for you? Essentially
BaseClass<any>
is as close to "someBaseClass<T>
for some typeT
I don't know and don't want to specify" as you can easily get. Maybe if there's ever some support added for existentially quantified generics you can do better, but for now I'd say just useBaseClass<any>
and move on. If that works I can write up an answer. If not, let me know what issues remain. – jcalz Commented May 16, 2021 at 17:48
1 Answer
Reset to default 12Firstly, the type
type IChildrenObj = {
[key: string]: InstanceType<typeof BaseClass>; // instances?
};
is not appropriate to describe your Children
object. Children
stores class constructors while InstanceType<typeof BaseClass>
, even if it worked for abstract classes (which, as you noted, it doesn't), would be talking about class instances. It would be closer to write
type IChildrenObj = {
[key: string]: typeof BaseClass; // more like constructors
};
But that is also not what Children
stores:
const Children: IChildrenObj = {
C1: Child1, // error!
// Type 'typeof Child1' is not assignable to type 'typeof BaseClass'.
// Construct signature return types 'Child1' and 'BaseClass<T>' are inpatible.
C2: Child2, // error!
// Type 'typeof Child2' is not assignable to type 'typeof BaseClass'.
// Construct signature return types 'Child2' and 'BaseClass<T>' are inpatible.
}
The type typeof BaseClass
has an abstract construct signature that looks something like new <T>() => BaseClass<T>
; the callers (or more usefully, the subclasses that extend BaseClass
) can choose anything they want for T
, and BaseClass
must be able to handle that. But the types typeof Child1
and typeof Child2
are not able to produce BaseClass<T>
for any T
that the caller of new Child1()
or the extender class Grandchild2 extends Child2
wants; Child1
can only construct a BaseClass<Item1>
and Child2
can only construct a BaseClass<Item2>
.
So currently IChildrenObj
says it holds constructors that can each produce a BaseClass<T>
for every possible type T
. Really what you'd like is for IChildrenObj
to say it holds constructors that can each produce a BaseClass<T>
for some possible type T
. That difference between "every" and "some" has to do with the difference between how the type parameter T
is quantified; TypeScript (and most other languages with generics) only directly supports "every", or universal quantification. Unfortunately there is no direct support for "some", or existential quantification. See microsoft/TypeScript#14446 for the open feature request.
There are ways to accurately encode existential types in TypeScript, but these are probably a little too annoying to use unless you really care about type safety. (But I can elaborate if this is needed)
Instead, my suggestion here is probably to value productivity over full type safety and just use the intentionally loose any
type to represent the T
you don't care about.
So, here's one way to define IChildrenObj
:
type SubclassOfBaseClass =
(new () => BaseClass<any>) & // a concrete constructor of BaseClass<any>
{ [K in keyof typeof BaseClass]: typeof BaseClass[K] } // the statics without the abstract ctor
/* type SubclassOfBaseClass = (new () => BaseClass<any>) & {
prototype: BaseClass<any>;
getName: () => string;
} */
type IChildrenObj = {
[key: string]: SubclassofBaseClass
}
The type SubclassOfBaseClass
is the intersection of: a concrete construct signature that produces BaseClass<any>
instances; and a mapped type which grabs all the static members from typeof BaseClass
without also grabbing the offending abstract construct signature.
Let's make sure it works:
const Children: IChildrenObj = {
C1: Child1,
C2: Child2,
} // okay
const nums = Object.values(Children)
.map(ctor => new ctor().itemArray.length); // number[]
console.log(nums); // [0, 0]
const names = Object.values(Children)
.map(ctor => ctor.getName()) // string[]
console.log(names); // ["Child1", "Child2"]
Looks good.
The caveat here is that, while IChildrenObj
will work, it's too fuzzy of a type to keep track of things you might care about, such as the particular key/value pairs of Children
, and especially the weird "anything goes" behavior of index signatures and the any
in BaseClass<any>
:
// index signatures pretend every key exists:
try {
new Children.C4Explosives() // piles okay, but
} catch (err) {
console.log(err); //
本文标签:
javascriptTypescript type of subclasses of an abstract generic classStack Overflow
版权声明:本文标题:javascript - Typescript: type of subclasses of an abstract generic class - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人,
转载请联系作者并注明出处:http://www.betaflare.com/web/1741469343a2380507.html,
本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论