TS2322:“ T”可以用与“ SuperClass”无关的任意类型实例化

时间:2020-08-09 03:05:06

标签: typescript

我有一个具有大量子类的基类。

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

3 个答案:

答案 0 :(得分:1)

尽管这要容易得多,但是您可以将InstanceTypegeneric parameterstype 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);
}

Playground

答案 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,那么日子快乐。但是,如果不是这样,并且TSuperClass的子类并带有其他成员,那么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。实际上,ASuperClass无关,而是A满足SuperClass的问题。我认为您的filterList函数中没有任何方法可以实际强制TSuperClass的子类。

答案 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的参数,并且很容易避免传递不可构造函数的逻辑错误。

我不认为有可能两全其美,要有参数安全性来限制无效的类/函数,同时还要允许具有有效的具体实现的抽象类。如果我对此有误,请纠正我(和/或发表您自己的答案)