使用泛型扩展接口后,不再能够分配给父接口。

4
我最近升级了TypeScript 2.4,并且遇到了几个错误,这些错误抱怨我的类型不再可分配。
以下是我遇到错误的情形:
interface Parent {
    prop: any
}

interface Child extends Parent {
    childProp: any
}

type Foo<T> = <P extends Parent>(parent: P) => T

function createFooFunction<T>(arg: T): Foo<T> {
    // Error here!
    return (child: Child): T => {
        return arg;
    }
}

在 TypeScript 2.3 中,这是可以接受的,但是在 TypeScript 2.4 中会产生错误。
Type '(child: Child) => T' is not assignable to type 'Foo<T>'.
Types of parameters 'child' and 'parent' are incompatible.
    Type 'P' is not assignable to type 'Child'.
    Type 'Parent' is not assignable to type 'Child'.
        Property 'childProp' is missing in type 'Parent'.

关于错误的最后一行,我注意到如果我将子元素的属性设置为可选的,那么typescript就会满意,即如果我进行以下更改

interface Child extends Parent {
    childProp?: any
}

尽管这不是一种理想的修复方式,因为在我的情况下,childProp是必需的。

我还注意到直接将Foo的参数类型更改为Parent也可以满足TypeScript的要求,即进行此更改:

type Foo<T> = (parent: Parent) => T

然而,这也不是解决方法,因为我无法控制Foo类型或Parent类型。它们都来自供应商的.d文件,因此我不能修改它们。

但无论如何,我不确定为什么会出现这种错误。Foo类型表明它需要继承自Parent的东西,而Child就是这样一个对象,那么为什么typescript认为它不可分配呢?

编辑:我将其标记为已回答,因为添加--noStrictGenericChecks标志可以抑制错误(在此被接受的答案)。然而,我仍然想知道为什么这是一个错误,因为如果我的代码有误,我宁愿保持严格检查并进行重构,而不是简单地绕过它。

因此,再次强调问题的核心,既然Child继承了Parent,为什么typescript不再认为Child可以分配给Parent,并且从面向对象的泛型角度看,为什么这样做更正确?

2个回答

4
我认为问题在于你返回了一个需要一个Child作为参数的函数,但将其分配给一个必须能够接受任何派生自Parent而不仅仅是Child的函数。
假设你调用createFooFunction<T>来获取Foo<T>。根据Foo<T>的定义,可以创建一个扩展ParentChild2类,并将其作为参数传递给你所获得的Foo<T>。这是一个问题,因为你实际上返回了一个只能得到Child作为参数的函数,永远不会得到Child2
这其实更正确。记住,你不是将一个Child赋值给Parent。你正在将只期望Child的函数分配给可以接受任何类型的Parent的函数。 这是非常不同的。另一方面,如果我们处理的是返回值而不是输入,那么这将是可以的。这就是协变性和反变性之间的区别,这可能在开始时有点令人困惑。在C#中,您可以使用inout关键字在自己的泛型类中指定这个问题。
例如,在C#中,如果你有一个IEnumerable<Child>(声明为IEnumerable<out T>),它也自然是一个IEnumerable<Parent>,因为你正在迭代 - 你得到Child对象 - 你还可以得到Parent对象,因为每个Child也是一个Parent。因此,您可以将IEnumerable<Child>分配给IEnumerable<Parent>,但反过来则不行!因为你不能保证得到一个Child对象。
另一方面,如果你有像IComparer<Parent>这样可以比较两个Parent对象的东西(声明为IComparer<in T>),那么由于每个Child也是一个Parent,它也可以比较任何两个Child对象。因此,您可以将IComparer<Parent>分配给IComparer<Child>,但反过来则不行 - 只能比较Child的东西只知道如何比较Child!这就是你在这里遇到的问题。
你可以将 inout 理解为回调函数中的输入(参数)和输出(返回值)。你只能使输入更加具体化(逆变),并使输出更加通用化(协变)。
顺便说一下,我认为在这里 <P extends Parent> 是完全没有用的(它会增加混乱),因为无论是否使用泛型,你都可以传递任何继承自 Parent 的东西到该函数中。只有当你像这样返回类型时才有用:<P extends Parent>(parent: P) => P 以保留正确的类型 P。如果没有泛型,你必须取 Parent 并返回 Parent,因此即使你放入了更具体的内容,你也会得到一个 Parent 返回。至于为什么如果去掉这个部分就会消除错误,我真的不知道。

3

添加 --noStrictGenericChecks 确实抑制了错误,所以我会将其标记为已回答。 - davidmk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接