打字稿:抽象泛型 class 的子 classes 类型
Typescript: type of subclasses of an abstract generic class
我有一个 Base 泛型 class:
abstract class BaseClass<T> {
abstract itemArray: Array<T>;
static getName(): string {
throw new Error(`BaseClass - 'getName' was not overridden!`);
}
internalLogic() {}
}
及继承人:
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";
}
}
现在我想定义一个以继承者为值的对象:
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,
}
最后,我希望能够使用子项的静态方法,并且能够创建它们的实例:
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)
*/
首先是类型
type IChildrenObj = {
[key: string]: InstanceType<typeof BaseClass>; // instances?
};
不适合描述您的Children
对象。 Children
存储 class 构造函数 而 InstanceType<typeof BaseClass>
,即使它适用于抽象 classes(正如你所指出的,它不会't),会谈论 class 个实例 。会更接近写
type IChildrenObj = {
[key: string]: typeof BaseClass; // more like constructors
};
但这也不是 Children
存储的内容:
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 incompatible.
C2: Child2, // error!
// Type 'typeof Child2' is not assignable to type 'typeof BaseClass'.
// Construct signature return types 'Child2' and 'BaseClass<T>' are incompatible.
}
类型 typeof BaseClass
有一个类似于 new <T>() => BaseClass<T>
的抽象构造签名;调用者(或者更有用的是,扩展 BaseClass
的子 classes)可以为 T
选择他们想要的任何东西,并且 BaseClass
必须能够处理它。但是类型 typeof Child1
和 typeof Child2
无法为 new Child1()
的调用者或扩展者 class Grandchild2 extends Child2
想要的任何 T
生成 BaseClass<T>
; Child1
只能构造一个BaseClass<Item1>
而Child2
只能构造一个BaseClass<Item2>
.
所以目前 IChildrenObj
表示它包含构造函数,每个构造函数都可以为 每个 可能的类型 T
生成一个 BaseClass<T>
。对于 IChildrenObj
来说,您真正想要的是说它包含构造函数,每个构造函数都可以为 一些 可能的类型 T
生成一个 BaseClass<T>
。 “每个”和“一些”之间的区别与类型参数 T
与开放功能请求的 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 之间的区别有关。
有一些方法可以在 TypeScript 中准确地 编码 存在类型,但除非您真的关心类型安全,否则使用这些方法可能有点烦人。 (但如果需要,我可以详细说明)
相反,我在这里的建议可能是重视生产力而不是完整的类型安全,并且只使用 the intentionally loose any
type 来表示您不关心的 T
。
所以,这是定义 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
}
类型 SubclassOfBaseClass
是 intersection of: a concrete construct signature that produces BaseClass<any>
instances; and a mapped type,它从 typeof BaseClass
中获取所有静态成员,而不同时获取有问题的抽象构造签名。
让我们确保它有效:
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"]
看起来不错。
这里需要注意的是,虽然 IChildrenObj
可以工作,但它的类型过于模糊,无法跟踪您可能关心的事情,例如特定的 key/value 对 Children
,尤其是 index signatures 和 BaseClass<any>
:
中的 any
奇怪的“任何事情都会发生”的行为
// index signatures pretend every key exists:
try {
new Children.C4Explosives() // compiles okay, but
} catch (err) {
console.log(err); // RUNTIME: Children.C4Explosives is not a constructor
}
// BaseClass<any> means you no longer care about what T is:
new Children.C1().itemArray.push("Hey, this isn't an Item1") // no error anywhere
所以在这种情况下,我的建议是仅确保 Children
可分配给 IChildrenObj
而无需实际注释。例如,您可以使用辅助函数:
const asChildrenObj = <T extends IChildrenObj>(t: T) => t;
const Children = asChildrenObj({
C1: Child1,
C2: Child2,
}); // okay
现在 Children
仍然可以在任何你需要 IChildrenObj
的地方使用,但它仍然会记住所有特定的 key/value 映射,因此当你做坏事时会发出错误:
new Children.C4Explosives() // compiler error!
//Property 'C4Explosives' does not exist on type '{ C1: typeof Child1; C2: typeof Child2; }'
new Children.C1().itemArray.push("Hey, this isn't an Item1") // compiler error!
// Argument of type 'string' is not assignable to parameter of type 'Item1'
如果需要,您仍然可以使用 IChildrenObj
:
const anotherCopy: IChildrenObj = {};
(Object.keys(Children) as Array<keyof typeof Children>)
.forEach(k => anotherCopy[k] = Children[k]);
我有一个 Base 泛型 class:
abstract class BaseClass<T> {
abstract itemArray: Array<T>;
static getName(): string {
throw new Error(`BaseClass - 'getName' was not overridden!`);
}
internalLogic() {}
}
及继承人:
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";
}
}
现在我想定义一个以继承者为值的对象:
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,
}
最后,我希望能够使用子项的静态方法,并且能够创建它们的实例:
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)
*/
首先是类型
type IChildrenObj = {
[key: string]: InstanceType<typeof BaseClass>; // instances?
};
不适合描述您的Children
对象。 Children
存储 class 构造函数 而 InstanceType<typeof BaseClass>
,即使它适用于抽象 classes(正如你所指出的,它不会't),会谈论 class 个实例 。会更接近写
type IChildrenObj = {
[key: string]: typeof BaseClass; // more like constructors
};
但这也不是 Children
存储的内容:
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 incompatible.
C2: Child2, // error!
// Type 'typeof Child2' is not assignable to type 'typeof BaseClass'.
// Construct signature return types 'Child2' and 'BaseClass<T>' are incompatible.
}
类型 typeof BaseClass
有一个类似于 new <T>() => BaseClass<T>
的抽象构造签名;调用者(或者更有用的是,扩展 BaseClass
的子 classes)可以为 T
选择他们想要的任何东西,并且 BaseClass
必须能够处理它。但是类型 typeof Child1
和 typeof Child2
无法为 new Child1()
的调用者或扩展者 class Grandchild2 extends Child2
想要的任何 T
生成 BaseClass<T>
; Child1
只能构造一个BaseClass<Item1>
而Child2
只能构造一个BaseClass<Item2>
.
所以目前 IChildrenObj
表示它包含构造函数,每个构造函数都可以为 每个 可能的类型 T
生成一个 BaseClass<T>
。对于 IChildrenObj
来说,您真正想要的是说它包含构造函数,每个构造函数都可以为 一些 可能的类型 T
生成一个 BaseClass<T>
。 “每个”和“一些”之间的区别与类型参数 T
与开放功能请求的 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 之间的区别有关。
有一些方法可以在 TypeScript 中准确地 编码 存在类型,但除非您真的关心类型安全,否则使用这些方法可能有点烦人。 (但如果需要,我可以详细说明)
相反,我在这里的建议可能是重视生产力而不是完整的类型安全,并且只使用 the intentionally loose any
type 来表示您不关心的 T
。
所以,这是定义 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
}
类型 SubclassOfBaseClass
是 intersection of: a concrete construct signature that produces BaseClass<any>
instances; and a mapped type,它从 typeof BaseClass
中获取所有静态成员,而不同时获取有问题的抽象构造签名。
让我们确保它有效:
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"]
看起来不错。
这里需要注意的是,虽然 IChildrenObj
可以工作,但它的类型过于模糊,无法跟踪您可能关心的事情,例如特定的 key/value 对 Children
,尤其是 index signatures 和 BaseClass<any>
:
any
奇怪的“任何事情都会发生”的行为
// index signatures pretend every key exists:
try {
new Children.C4Explosives() // compiles okay, but
} catch (err) {
console.log(err); // RUNTIME: Children.C4Explosives is not a constructor
}
// BaseClass<any> means you no longer care about what T is:
new Children.C1().itemArray.push("Hey, this isn't an Item1") // no error anywhere
所以在这种情况下,我的建议是仅确保 Children
可分配给 IChildrenObj
而无需实际注释。例如,您可以使用辅助函数:
const asChildrenObj = <T extends IChildrenObj>(t: T) => t;
const Children = asChildrenObj({
C1: Child1,
C2: Child2,
}); // okay
现在 Children
仍然可以在任何你需要 IChildrenObj
的地方使用,但它仍然会记住所有特定的 key/value 映射,因此当你做坏事时会发出错误:
new Children.C4Explosives() // compiler error!
//Property 'C4Explosives' does not exist on type '{ C1: typeof Child1; C2: typeof Child2; }'
new Children.C1().itemArray.push("Hey, this isn't an Item1") // compiler error!
// Argument of type 'string' is not assignable to parameter of type 'Item1'
如果需要,您仍然可以使用 IChildrenObj
:
const anotherCopy: IChildrenObj = {};
(Object.keys(Children) as Array<keyof typeof Children>)
.forEach(k => anotherCopy[k] = Children[k]);