派生类是否实现基类的接口?
例子:
interface IBase {
controls : { [key: string] : number }
}
class BaseClass implements IBase {
controls = {};
}
class TestClass extends BaseClass {
controls = {test:'a'};
}
当接口声明它应该是一个数字时,当将控件值设置为派生类中的字符串时,上面不会抱怨。
如果我在派生类中将控件设置为 null,它会抱怨:
类型“null”不可分配给类型“{}”
在每个派生类和类中实现接口的正确方法是什么?
当一个类implements
成为一个接口时,它实际上并不影响类的类型。编译器检查类是否与接口兼容,但它不使用接口作为上下文来为类的成员提供类型。如果您编写class Foo implements Bar {...}
并且没有编译器错误,那么编译器将对其进行处理,就好像您遗漏了implements Bar
而只是编写了class Foo {...}
. 有关更多信息,请参阅microsoft/TypeScript#32082和其中链接的问题。
SoBaseClass
的controls
属性被推断为具有空对象类型{}
。您可能期望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 B
and 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
因为controls
in的属性TestClass
有一个BaseClass
( {}
)中没有提到的额外属性,可以通过添加属性来扩展对象类型。
并且BaseClass extends TestClass
因为它的controls
属性{}
具有与索引签名冲突的属性(它根本没有属性)......并且 TypeScript 将为这些类型提供隐式索引签名。
但是当然TestClass
不会扩展,IBase
因为额外的属性和隐式索引签名是不相互一致的。所以我们有奇怪的地方。
在你的情况下,我可能会建议明确的注释,因为这是你的意图。但是您迟早会遇到不健全的情况,您应该为此做好准备。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句