typescript - 使用通用的扩展接口(interface)不再可分配给父级

标签 typescript generics typescript2.0 typescript2.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'.

关于错误的最后一行,我注意到如果我将 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 extends Parent,为什么 typescript 不再认为 Child 可以分配给 Parent,并且就 OOP 泛型而言,为什么它比以前更正确?

最佳答案

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

假设您调用 createFooFunction<T>得到Foo<T> .根据 Foo<T> 的定义, 可以创建一个类 Child2延伸 Parent并将其作为参数传递给 Foo<T>你得到的。这是一个问题,因为您实际上返回了一个只能获取 Child 的函数。作为论据,从不 Child2 .

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

例如,在 C# 中,如果您有一个 IEnumerable<Child> (声明为 IEnumerable<out T> ),它自然也是一个 IEnumerable<Parent> ,因为你正在迭代 - 你得到 Child对象 - 你也可以得到 Parent反对,因为每Child也是一个Parent .因此,您可以分配 IEnumerable<Child>IEnumerable<Parent>但不是相反!因为你不能保证得到 Child对象。

另一方面,如果你有类似 IComparer<Parent> 的东西(声明为 IComparer<in T> )可以比较两个 Parent对象 - 自从每个 Child也是一个Parent , 它也可以比较任意两个 Child对象。因此,您可以分配 IComparer<Parent>IComparer<Child> ,但不是相反 - 可以比较 Child 的东西只知道如何比较一个Child !这是你的问题。

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

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

关于typescript - 使用通用的扩展接口(interface)不再可分配给父级,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44917558/

相关文章:

typescript - 如何禁用生成 "exports.__esModule = true;"和 "require("lib");"

scala - 是否可以创建可用作 T[A, B] 的泛型类型 T[A < : C[U], B < : C[U], U]?

javascript - 如何在new里面填充object JS?

typescript - 如何为包中的类型指定最低 Typescript 版本?

reactjs - 输入 '{ ' x-access-token' : any; } | { 'x-access-token' ?:未定义; }' is not assignable to type ' AxiosRequestHeaders |不明确的'

typescript - 本地链接 Vue 应用程序和 Vue 库 --> d.ts 问题;

java - 为什么 varargs (Class<? extends Throwable>... t) "unchecked or unsafe"操作?

C# 为什么其中一些使用委托(delegate)和泛型的方法没有启动?

ngrx - @ngrx/Store 如何用于缓存?

typescript - 使用构造函数时找不到名称,但在转换时可以找到名称