我有一个具有大量子类的基类。
class SuperClass{
constructor(){}
}
class SubClass1 extends SuperClass{
constructor(){super()}
SubClass1Prop(){}
}
class SubClass2 extends SuperClass{
constructor(){super()}
SubClass2Prop(){}
}
我有一个包含不同子类混合的列表,并且我将其类型声明为SuperClass[]
,因为它们共享相同的祖先。
let list:SuperClass[] = [];
list.push(new SubClass1());
list.push(new SubClass2());
我有一个函数,该函数将SubClass作为参数,然后过滤列表以返回该SubClass的实例。
此函数的类型声明取自该SO答案(Type for function that takes a class as argument, and returns an instance of that class),与我返回的是列表而不是单个对象的差别很小。
错误就在这里。我控制可以将哪些类型传递给filterList函数,并可以保证它将始终是SuperClass的子级。但是,TypeScript无法知道这一点。如何声明我传递给此函数的任何className必须始终是SuperClass的子级,并且不能为“任意类型”?
function filterList<T>(className: { new():T }):T[] {
//TS2322: Type 'SuperClass[]' is not assignable to type 'T[]'.
// Type 'SuperClass' is not assignable to type 'T'.
// 'T' could be instantiated with an arbitrary type which could be unrelated to 'SuperClass'
return list.filter(obj => obj instanceof className);
}
上述功能的示例使用场景。 (为了演示目的,已删除了无效安全检查以简化操作)
我要求filteredList被正确地标识为具有类型SubClass1[]
,而不是类型SuperClass[]
或any[]
,而无需使用“ as”关键字显式地进行类型转换。当前满足了所有这些要求,但是TS错误在上面的函数中。任何解决方案都不得破坏此处的类型检查。
let filteredList = filterList(SubClass1);
filteredList[0].SubClass1Prop();
答案 0 :(得分:1)
尽管这要容易得多,但是您可以将InstanceType
与generic parameters和type guards结合使用以达到相同的效果:
function filterList<T extends { new (...args: any): any }>(className: T): InstanceType<T>[] {
const isT = (potentialT: any): potentialT is InstanceType<T> => {
return potentialT instanceof className;
}
return list.filter<InstanceType<T>>(isT);
}
答案 1 :(得分:0)
list
的类型为SuperClass[]
,因此对其进行过滤将使您返回另一个SuperClass[]
,因此该函数返回的类型为SuperClass[]
。但是,根据函数的类型,它实际上返回类型T[]
。如果T
满足SuperClass
,例如您调用它的示例方式,那么祝您开心。但是,如果T
不满足SuperClass
,那么您返回的过滤列表将根本无法分配给函数的实际返回类型。要解决此问题,您只需确保T
满足SuperClass
。使用类型参数T extends SuperClass
可以很容易地做到这一点。
您应该始终约束类型参数(请参阅the docs),除非它们的类型从字面上看无关紧要,即使它没有错误也是如此。用不满足filterList()
的类来调用SuperClass
根本没有任何意义,因此您应该告诉编译器在尝试时停止运行。在这种情况下,它停止了您的操作,甚至没有您进行尝试,因为它识别出它本来就不安全。
您会注意到,进行更改会给您带来另一个错误:
'SuperClass'可分配给类型'T'的约束,但是'T'可以用约束'SuperClass'的其他子类型实例化。
这里的问题与以前相同:函数返回类型SuperClass[]
,但其类型为返回T[]
。如果T
是 SuperClass
,那么日子快乐。但是,如果不是这样,并且T
是SuperClass
的子类并带有其他成员,那么SuperClass[]
将无法分配给T[]
。
考虑到您显然从非SuperClass[]
类型的数组中过滤掉了所有内容,这表明过滤后的数组实际上是T
类型的,这可能与直觉相反应为T[]
类型。但这并不是完全可行的。类型保护obj instanceof className
创建的语义是obj
是类型T
,因此从回调的那一点开始,编译器就会知道这一点。但是,没有向调用该回调的人提供此含义。您的Array.prototype.filter()
调用不知道哪种类型满足其回调,因为该回调不会告诉您。它所知道的只是它要过滤的数组是SuperClass[]
类型,因此,最好的说是结果数组也是SuperClass[]
类型。
解决方案很简单:您只需要在回调中提供类型谓词(请参见the docs)。由此,filter()
将知道哪些类型满足其回调,从而可以准确地说出结果数组是什么类型。
const foo: SuperClass[] = list.filter(obj => obj instanceof className)
const bar: T[] = list.filter((obj): obj is T => obj instanceof className)
因此,通过这两个修复程序,您的filterList()
函数现在看起来像这样:
function filterList<T extends SuperClass>(className: { new():T }):T[] {
return list.filter((obj): obj is T => obj instanceof className);
}
我对您要在这里做什么感到怀疑;概括地说,我不确定这是个好主意。您应该知道,类继承本身在类型方面没有语义。给定class A {}
,new A
将可分配为类型SuperClass
。实际上,A
与SuperClass
无关,而是A
满足SuperClass
的问题。我认为您的filterList
函数中没有任何方法可以实际强制T
是SuperClass
的子类。
答案 2 :(得分:0)
我必须添加一个答案,因为我发现了原始问题未明确询问的一些新信息(关于抽象类)。
function filterList<T extends SuperClass>(className: Function & {prototype: T}): T[] {
return list.filter((e): e is T => e instanceof className);
}
此解决方案基于matthewlucock(https://stackoverflow.com/a/63323255/14073412)给出的解决方案。
此处的区别在于,函数参数的类型为Function & {prototype: T}
而不是{ new():T }
。
关键问题是抽象类不可构造(“新的”)。如果希望将抽象类传递给filterList()
并能够获取实现抽象类的所有对象,则使用{ new():T }
会失败,并显示以下错误:
不能将抽象构造函数类型分配给非抽象构造函数类型。(2345)
与Function
的交集对于确保传递的参数是函数而不是常规对象是必需的。没有它,就不能使用instanceof
运算符:
“ instanceof”表达式的右侧必须为“ any”类型或可分配给“ Function”接口类型的类型。(2359)
请注意,尽管此解决方案允许将抽象类传递给过滤器函数,但作为一个权衡,您在确保传递的参数实际上是一个类时会损失一些类型安全性。尝试使用非类函数调用filterList
不会产生TypeScript错误:
function NonConstructibleFunction(){}
filterList(NonConstructibleFunction);
但是,只要类属性/方法的形状不匹配,TypeScript就会捕获由于意外传入不相关的类而引起的错误。请注意,未检查构造函数的形状。
class UnrelatedDummyClass{
constructor(differentConstructorParameters:number){}
}
//Property 'commonFunc' is missing in type 'UnrelatedDummyClass' but required in type 'SuperClass'.(2345)
filterList(UnrelatedDummyClass);
参见演示:Playground Link
对我来说,这种解决方案是可取的,因为在我的用例中,我可以控制传递给filterList
的参数,并且很容易避免传递不可构造函数的逻辑错误。>
我不认为有可能两全其美,要有参数安全性来限制无效的类/函数,同时还要允许具有有效的具体实现的抽象类。如果我对此有误,请纠正我(和/或发表您自己的答案)