当我尝试在if-else语句中使用instanceof
派生类实例时遇到问题。请考虑以下示例:
interface IBaseModel {
id: string
}
class BaseClass {
model: IBaseModel
constructor() {
}
setModel(model: IBaseModel) {
this.model = model
}
getValueByName(name: string) {
return this.model[name];
}
}
interface IDerived1Model extends IBaseModel {
height: number;
}
class Derived1 extends BaseClass {
setModel(model: IDerived1Model) {
super.setModel(model);
// Do something with model...
}
}
interface IDerived2Model extends IBaseModel {
width: number;
}
class Derived2 extends BaseClass {
setModel(model: IDerived2Model) {
super.setModel(model);
// Do something with model...
}
}
const model1 = { id: "0", height: 42 };
const model2 = { id: "1", width: 24 };
const obj1 = new Derived1();
obj1.setModel(model1);
const obj2 = new Derived2();
obj2.setModel(model2);
const objs: BaseClass[] = [
obj1,
obj2
];
let variable: any = null;
for (const obj of objs) {
if (obj instanceof Derived1) {
variable = obj.getValueByName("height"); // Ok, obj is now of type `Derived1`
} else if (obj instanceof Derived2) {
variable = obj.getValueByName("width"); // Does not compile: Property 'getValueByName' does not exist on type 'never'
}
console.log("Value is: " + variable);
}
此处getValueByName
部分的obj
无法调用else
,因为它缩小为never
。不知何故,Typescript认为永远不会执行else
,但这是错误的。
要注意的重要一点是覆盖函数setModel
。覆盖具有不同的参数类型,但这些类型继承自基类IBaseModel
类型。如果我将它们更改为基本类型,那么Typescript不会抱怨并编译好:
class Derived1 extends BaseClass {
setModel(model: IBaseModel) {
super.setModel(model);
// Do something with model...
}
}
class Derived2 extends BaseClass {
setModel(model: IBaseModel) {
super.setModel(model);
// Do something with model...
}
}
所以我的问题是,为什么使用不同类型的覆盖会使instanceof
运算符将对象的类型缩小为never
?这是设计的吗?
使用Typescript 2.3.4,2.4.1和Typescript Playground进行了测试。
谢谢!
答案 0 :(得分:1)
欢迎来到TypeScript Issue #7271的世界!你被TypeScript' structural typing及其与instanceof
的奇怪(和坦率的不健全)互动所咬。
TypeScript将Derived1
和Derived2
视为完全相同的类型,因为它们具有相同的结构形状。如果obj instanceof Derived1
返回false
,那么TypeScript编译器会同时认为"好的,obj
不是Derived1
"和#34;好的,obj
不是Derived2
",因为它没有看到它们之间的区别。然后当您检查obj instanceof Derived2
返回true
时,编译器说" Gee,obj
两者都是Derived2
。这可能会never
发生。"当然, 在运行时1}}和Derived1
之间存在差异,并且可以发生。这是你的问题。
解决方案:将一些不同的属性推送到Derived2
和Derived1
,以便TypeScript可以区分它们。例如:
Derived2
现在,每个类上都有一个可选的class Derived1 extends BaseClass {
type?: 'Derived1'; // add this line
setModel(model: IDerived1Model) {
super.setModel(model);
// Do something with model...
}
}
class Derived2 extends BaseClass {
type?: 'Derived2'; // add this line
setModel(model: IDerived2Model) {
super.setModel(model);
// Do something with model...
}
}
属性,具有不同的字符串文字类型(不更改发出的JavaScript)。 TypeScript现在意识到type
与Derived1
不同,您的错误就会消失。
希望有所帮助。祝你好运!
@ sebastien-grenier said:
哇,这很奇怪。看起来在某些时候有一个change (#10216)来修复问题#7271的一些实例,但是您设法找到了一个新实例。我的猜测就是因为你用较窄的参数类型覆盖了感谢您的解释!但是,当覆盖中的参数类型不同时,我没有看到为什么Typescript认为它们在结构上相同,但是当类型相同时(即与父代
Derived2
相同),一切都编译得很好。另外,如果我的对象上已经有一个名为IBaseModel
的成员,会发生什么?它可能与type
发生冲突吗? ?。谢谢!
type
方法(顺便说一下......顺便说一下......每个setModel
应该有一个BaseClass
接受任何 setModel()
。如果你对我们这样做很有兴趣,我们可以谈谈),它会将#10216中的代码更改变为不适用。这可能是一个错误...你可能想要file it。
是的,如果您已拥有具有相同密钥的属性,则应选择一个新密钥。我们的想法是品牌化。如果您担心意外冲突,可以选择IBaseModel
这样的名称。
但是你可以做的更直接的改变不会发生冲突:
__typeBrand
据推测,你希望每个班级都知道它的class Derived1 extends BaseClass {
model: IDerived1Model;
// your overrides follow
}
class Derived2 extends BaseClass {
model: IDerived2Model;
// your overides follow
}
是缩小的类型,对吗?因此,执行上述model
的缩小都可以让编译器知道类型在结构上是不同的,并使派生类更安全。
干杯!