【问题标题】:Extending interface with generic no longer assignable to parent使用泛型扩展接口不再可分配给父级
【发布时间】:2023-03-21 07:34:01
【问题描述】:

我最近升级到 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'.

关于错误的最后一行,我注意到如果我将 Child 的属性设为可选,那么 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 标志将抑制错误 (accepted answer here)。但是,我仍然想知道为什么首先这是一个错误,因为我宁愿严格检查并在错误时重构我的代码,而不是仅仅将其短路。

所以要重申问题的核心,既然 Child 扩展了 Parent,为什么打字稿不再认为 Child 可以分配给 Parent,并且就 OOP 泛型而言,为什么这比以前更正确?

【问题讨论】:

    标签: typescript generics typescript2.0 typescript2.4


    【解决方案1】:

    我认为问题在于您要返回一个需要 Child 作为参数的函数,但将其分配给一个必须能够接受从 Parent 派生的任何内容的函数,而不仅仅是 Child .

    假设您调用createFooFunction&lt;T&gt; 得到Foo&lt;T&gt;。根据Foo&lt;T&gt; 的定义,可以创建一个扩展Parent 的类Child2,并将其作为参数传递给你得到的Foo&lt;T&gt;。这是一个问题,因为您实际上返回的函数只能以 Child 作为参数,而不是 Child2

    这实际上更正确。请记住,您不会将Child 分配给Parent。您正在使用一个只需要 Child 的函数,并将其分配给可以采用任何类型 Parent 的函数。这是非常不同的。另一方面,如果我们处理的是返回值而不是输入,那就没问题了。这是协变和逆变之间的区别,一开始可能会有些混乱。在 C# 中,您可以使用 inout 关键字在自己的泛型类中指定它。

    例如,在 C# 中,如果你有一个 IEnumerable&lt;Child&gt;(声明为 IEnumerable&lt;out T&gt;),它自然也是一个 IEnumerable&lt;Parent&gt;,因为你在迭代 - 你得到了 Child 对象 - 你也可以取出Parent 对象,因为每个Child 也是一个Parent。因此,您可以将IEnumerable&lt;Child&gt; 分配给IEnumerable&lt;Parent&gt;,但不能反过来!因为不能保证你会得到一个Child 对象。

    另一方面,如果你有类似IComparer&lt;Parent&gt;(声明为IComparer&lt;in T&gt;)的东西,它可以比较两个Parent 对象 - 因为每个Child 也是一个Parent,它也可以比较任何两个Child 对象。因此,您可以将IComparer&lt;Parent&gt; 分配给IComparer&lt;Child&gt;,但不能反过来——可以比较Child 的东西只知道如何比较Child!这是你的问题。

    您可以将inout 理解为回调中的输入(参数)和输出(返回)值。您只能使输入更具体(逆变),输出更通用(协方差)。

    顺便说一句,我认为 &lt;P extends Parent&gt; 在这里完全没用(而且它增加了混乱),因为你可以在该函数中传递任何扩展 Parent 的东西,即使它不是通用的。这仅在您返回如下类型时才有用:&lt;P extends Parent&gt;(parent: P) =&gt; P 以在返回值中保留正确的类型 P。如果没有泛型,您必须接受Parent 并返回Parent,因此即使您输入更具体的内容,您也会得到Parent。至于为什么如果你摆脱这个错误就会消失,我真的不知道。

    【讨论】:

      【解决方案2】:

      2.4 版引入了更严格的泛型检查

      阅读此https://blogs.msdn.microsoft.com/typescript/2017/06/27/announcing-typescript-2-4/ 并查找标题更严格的泛型检查

      来自文章:

      作为任何损坏的临时解决方法,您可以使用新的--noStrictGenericChecks 标志来抑制其中一些错误。

      它还为回调引入了严格的逆变性。

      【讨论】:

      • 添加 --noStrictGenericChecks 确实抑制了错误,所以我将其标记为已回答。
      猜你喜欢
      • 2019-11-11
      • 1970-01-01
      • 2020-06-18
      • 2021-02-06
      • 1970-01-01
      • 2021-08-28
      • 1970-01-01
      • 2018-04-09
      • 1970-01-01
      相关资源
      最近更新 更多