派生类是否实现基类的接口?
示例:
interface IBase {
controls : { [key: string] : number }
}
class BaseClass implements IBase {
controls = {};
}
class TestClass extends BaseClass {
controls = {test:'a'};
}
当接口声明它应该是一个数字时,在派生类中将控件值设置为字符串时,上面不会抱怨。
如果我在派生类中将控件设置为 null,它会抱怨:
<块引用>类型“null”不可分配给类型“{}”
在每个派生类和类中实现接口的方式是否正确?
答案 0 :(得分:3)
当一个类 implements
是一个接口时,它实际上并不影响类的类型。编译器检查类是否与接口兼容,但它不使用接口作为上下文来为类的成员提供类型。如果您编写 class Foo implements Bar {...}
并且没有编译器错误,那么编译器会像您省略 implements Bar
而只编写 class Foo {...}
一样对待它。有关详细信息,请参阅 microsoft/TypeScript#32082 和其中链接的问题。
因此推断 BaseClass
的 controls
属性具有空对象类型 {}
。您可能希望 BaseClass
的 controls
属性属于 { [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 的类型系统并不完全是 sound;有一些“漏洞”,TypeScript 允许分配不一致。
在健全的类型系统中,subtyping 应该是 transitive;这意味着,对于任何类型 A
、B
和 C
,如果 A extends B
和 B extends C
,则 A extends C
。虽然这在 TypeScript 中很常见,但有时会被违反。这就是您的示例中发生的情况:TestClass extends BaseClass
和 BaseClass extends IBase
,但 TestClass
不扩展 IBase
。
如果我们创建一个仅在 VerifyExtends<T, U>
时才编译的辅助类型函数 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
因为 controls
中的 TestClass
属性有一个额外的属性在 BaseClass
({}
) 中没有提到,你被允许通过添加属性来扩展对象类型。
并且 BaseClass extends TestClass
因为它的 controls
属性 {}
具有与索引签名冲突的属性(它根本没有属性)......并且 TypeScript 将给出 implicit index signatures到这种类型。
但当然,TestClass
不会扩展 IBase
,因为额外的属性和隐式索引签名不是相互一致的。所以我们有怪癖。
在您的情况下,我可能会建议使用明确的注释,因为这是您的意图。但你迟早会遇到不健全的情况,你应该为此做好准备。