Typescript TS2322:';T';可以使用与';无关的任意类型进行实例化;超类&x27;

Typescript TS2322:';T';可以使用与';无关的任意类型进行实例化;超类&x27;,typescript,Typescript,我有一个基类,它有大量的子类 class SuperClass{ constructor(){} } class SubClass1 extends SuperClass{ constructor(){super()} SubClass1Prop(){} } class SubClass2 extends SuperClass{ constructor(){super()} SubClass2Prop(){} } 我有一个包含不同子类的混合列表,我将其类

我有一个基类,它有大量的子类

class SuperClass{
    constructor(){}
}
class SubClass1 extends SuperClass{
    constructor(){super()}
    SubClass1Prop(){}
}
class SubClass2 extends SuperClass{
    constructor(){super()}
    SubClass2Prop(){}
}
我有一个包含不同子类的混合列表,我将其类型声明为
超类[]
,因为它们共享相同的祖先

let list:SuperClass[] = [];
list.push(new SubClass1());
list.push(new SubClass2());
我有一个函数,它接受一个子类作为参数,然后过滤列表以返回该子类的实例

此函数的类型声明取自此SO answer(),但有一个小区别,即我返回的是一个列表,而不是单个对象

错误就在这里。我控制可以传递到filterList函数中的类型,并保证它始终是超类的子类。然而,TypeScript无法知道这一点。如何声明传递给此函数的任何类名必须始终是超类的子类,并且不能是“任意类型”?

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);
}

虽然这非常简单,但您可以与和一起使用,以达到相同的效果:

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);
}
函数过滤器列表(类名:T):InstanceType[]{
const isT=(potentialT:any):potentialT是InstanceType=>{
返回类名称的潜在实例;
}
返回列表。过滤器(isT);
}

列表
是类型
超类[]
,因此过滤它将返回另一个
超类[]
,因此函数返回类型
超类[]
。但是,根据函数的类型,它实际上返回type
T[]
。如果
T
满足
SuperClass
,例如您所称的示例方式,那么祝您快乐。但是如果
T
不满足
超类
,那么返回的过滤列表将无法分配给函数的实际返回类型。要解决这个问题,只需确保
T
满足
超类
。使用类型参数
T extends SuperClass
可以轻松实现这一点

您应该始终约束您的类型参数(请参见),除非它们的类型实际上并不重要,即使不会出错。使用不满足
超类
的类调用
filterList()
是没有意义的,因此如果您尝试,应该告诉编译器停止。在这种情况下,它甚至在你不尝试的情况下就阻止了你,因为它认为它本质上是不安全的

但是,您会注意到,进行更改会导致另一个错误:

“超类”可分配给类型为“T”的约束,但“T”可以用约束“超类”的不同子类型实例化

这里的问题与前面一样:函数返回type
SuperClass[]
,但它的类型为returning
T[]
。如果
T
SuperClass
,那么快乐的日子就来了。但是如果它不是,并且
t
超类
的一个子类,并且有额外的成员,那么
SuperClass[]
将不能分配给
t[]

考虑到您正在从数组中筛选出不是类型
t
的所有内容,过滤后的列表实际上是类型
SuperClass[]
,这可能与直觉相反,这表明过滤后的数组应该是类型
t[]
。但这样做不太管用。className的类型guard
obj instanceof
创建了语义含义,即
obj
是类型
T
,因此从回调的那一点开始,编译器就知道了这一点。但是,对于调用回调的人,并没有提供这种含义。您的
Array.prototype.filter()
调用不知道哪些类型满足其回调,因为回调没有告诉它。它所知道的只是它所过滤的数组的类型是
超类[]
,因此它所能说的最好的结果就是生成的数组也是
超类[]
类型

解决方法很简单:只需在回调上提供一个类型谓词(请参阅)。由此,
filter()
将知道哪些类型满足其回调,从而使其能够准确地说出结果数组的类型

const foo:SuperClass[]=list.filter(obj=>obj instanceof className)
常量栏:T[]=list.filter((obj):obj是T=>obj类名称的实例)
因此,通过这两个修复,您的
filterList()
函数现在看起来如下所示:

函数过滤器列表(类名:{new():T}):T[]{
return list.filter((obj):obj是T=>obj instanceof className);
}
在一边
我怀疑你在这里想做什么;总之,我不确定这是否是个好主意。您应该知道,类继承本身在类型方面没有语义意义。给定
类A{}
新的A
将可分配给类型
超类
。这不是
a
实际上与
超类相关的问题,而是
a
满足
超类的问题。我认为在你的
filterList
函数中没有任何方法可以强制执行
t
超类的子类

我必须添加一个答案,因为我发现了一些原始问题没有明确提出的新信息(关于抽象类)

function filterList<T extends SuperClass>(className: Function & {prototype: T}): T[] {
    return list.filter((e): e is T => e instanceof className);
}
但是,如果类属性/方法的形状不匹配,TypeScript将捕获意外传入不相关类的错误。请注意,未选中构造函数形状

class UnrelatedDummyClass{
    constructor(differentConstructorParameters:number){}
}
//Property 'commonFunc' is missing in type 'UnrelatedDummyClass' but required in type 'SuperClass'.(2345)
filterList(UnrelatedDummyClass);
请参见演示:

对我来说,这个解决方案更可取,因为在我的用例中,我控制着传递给
filterList
的参数,并且很容易避免传递不可构造函数的逻辑错误

我认为不可能两全其美,让参数安全性限制无效的类/函数,同时允许
function NonConstructibleFunction(){}
filterList(NonConstructibleFunction);
class UnrelatedDummyClass{
    constructor(differentConstructorParameters:number){}
}
//Property 'commonFunc' is missing in type 'UnrelatedDummyClass' but required in type 'SuperClass'.(2345)
filterList(UnrelatedDummyClass);