首页 > 解决方案 > 派生类是否实现了 Typescript 中基类的接口

问题描述

派生类是否实现了基类的接口?

例子:

interface IBase {
    controls : { [key: string] : number }
}

class BaseClass implements IBase {
    controls = {};
}

class TestClass extends BaseClass {
    controls = {test:'a'};
}

当接口声明它应该是数字时,在派生类中将控件值设置为字符串时,上述内容不会抱怨。

如果我在派生类中将控件设置为 null,它会抱怨:

类型“null”不可分配给类型“{}”

在每个派生类和类中实现接口的正确方法是什么?

标签: typescript

解决方案


当一个类implements成为接口时,它实际上并不影响类的类型。编译器检查类是否与接口兼容,但它不使用接口作为上下文来为类的成员提供类型。如果您编写class Foo implements Bar {...}并且没有编译器错误,那么编译器会将其视为与您遗漏implements Bar并仅编写class Foo {...}. 有关详细信息,请参阅microsoft/TypeScript#32082和其中链接的问题。

SoBaseClasscontrols属性被推断为具有空对象类型{}。您可能期望BaseClass' 的controls属性为 type { [key: string] : number },但这不会发生,因为此类信息仅来自 子句,在为' 的成员implements IBase提供类型时会忽略该子句。BaseClass如果您想查看实现类或子类具有特定类型的属性,您应该注释它们:

class BaseClass implements IBase {
    controls: { [key: string]: number } = {}; // annotated
}

class TestClass extends BaseClass {
    // you might also want to annotate this, depending on intent
    controls = { test: 'a' }; // error!
}

同样重要的是要注意,无论好坏,TypeScript 的类型系统并不完全健全TypeScript 允许分配不一致的地方有一些“漏洞” 。

在健全的类型系统中,子类型应该是可传递的;这意味着,对于任何类型A, B, and C, if A extends Band B extends C, then A extends C。虽然这在 TypeScript 中很常见,但有时会被违反。这就是您的示例中发生的情况:TestClass extends BaseClass, 和BaseClass extends IBase, 但TestClass不扩展IBase.

如果我们创建一个VerifyExtends<T, U>只编译 if的辅助类型函数T extends U,我们可以见证子类型化的这种不传递性:

type VerifyExtends<T extends U, U> = void;    
type AB = VerifyExtends<TestClass, BaseClass> // okay
type BC = VerifyExtends<BaseClass, IBase> // okay
type AC = VerifyExtends<TestClass, IBase> // error!

TestClass extends BaseClass因为controlsin 的属性有一个在( )TestClass中没有提到的额外属性,并且允许您通过添加属性来扩展对象类型。BaseClass{}

并且BaseClass extends TestClass因为它的controls属性{}具有与索引签名冲突的属性(它根本没有属性)......并且 TypeScript 将为此类类型提供隐式索引签名。

但当然,TestClass不会扩展IBase,因为额外的属性和隐式索引签名不是相互一致的。所以我们有奇怪的地方。


在你的情况下,我可能会建议明确的注释,因为这是你的意图。但是你迟早会遇到不健全的,你应该为此做好准备。

Playground 代码链接


推荐阅读