在if-else语句中,使用instanceof将Typescript类型缩小为never
当我试图在if-else语句中将在if-else语句中,使用instanceof将Typescript类型缩小为never,typescript,instanceof,type-narrowing,Typescript,Instanceof,Type Narrowing,当我试图在if-else语句中将instanceof与派生类实例一起使用时,我遇到了一个问题。考虑下面的例子: interface IBaseModel { id: string } class BaseClass { model: IBaseModel constructor() { } setModel(model: IBaseModel) { this.model = model } getValueByName
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
不能在else
部分中的obj
上调用,因为它被缩小为never
。不知何故,Typescript认为永远不会执行else
,但这是错误的
需要注意的重要事项是重写函数setModel
。替代具有不同的参数类型,但这些类型继承自基本的IBaseModel
type。如果我将它们更改为基本类型,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
操作符将对象的类型缩小到从不?这是故意的吗
这是用Typescript 2.3.4、2.4.1和在Typescript操场上测试的
谢谢 欢迎来到人类的世界!TypeScript及其与instanceof
的奇怪(坦率地说是不健全)交互让您感到痛苦
TypeScript将Derived1
和Derived2
视为完全相同的类型,因为它们具有相同的结构形状。如果Derived1的obj实例返回false
,则TypeScript编译器认为“好的,obj
不是Derived1
”和“好的,obj
不是Derived2
”,因为它们之间没有区别。然后,当您检查Derived2的obj实例是否返回true
时,编译器会说“哎呀,obj
都是和不是一个Derived2
。这可能永远不会发生。”当然,Derived1
和Derived2
在运行时是有区别的,这是可能发生的。这是你的问题
解决方案是:将一些不同的属性放入Derived1
和Derived2
中,以便TypeScript可以区分它们之间的差异。例如:
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...
}
}
现在,每个类都有一个可选的type
属性,该属性具有不同的字符串文本类型(无需更改发出的JavaScript)。TypeScript现在意识到Derived1
与Derived2
不同,错误消失了
希望有帮助。祝你好运
更新1
@塞巴斯蒂安·格雷尼尔:
谢谢你的解释!但是,我不明白为什么Typescript在重写中的参数类型不同时认为它们在结构上是相同的,但在类型相同时(即与父级相同,IBaseModel
)一切都可以正常编译。另外,如果我的对象上已经有一个名为type
的成员,会发生什么情况?它是否与类型冲突?。谢谢
哇,真奇怪。看起来在某个时候有一个解决问题的方法#7271,但您设法找到了一个新的方法。我的猜测是,因为您使用更窄的参数类型覆盖了setModel
方法(顺便说一句,这是不合理的……每个BaseClass
都应该有一个setModel()
来接受任何IBaseModel
。如果您有兴趣这样做,我们可以谈谈),它愚弄了#10216中的代码更改,使其无法应用。这可能是一个错误。。。你可能想
是的,如果已经有一个具有相同键的属性,则应选择一个新属性。我们的想法是给这些类型打上品牌。如果您担心意外冲突,可以选择一个类似于\u typeBrand
的名称
但你可以做一个更直接的改变,不会产生冲突:
class Derived1 extends BaseClass {
model: IDerived1Model;
// your overrides follow
}
class Derived2 extends BaseClass {
model: IDerived2Model;
// your overides follow
}
想必你想让每个类都知道它的模型是缩小的类型,对吧?因此,对model
进行上述缩小操作既可以让编译器知道类型在结构上是不同的,也可以让派生类更安全地使用
干杯 谢谢你的解释!但是,我不明白为什么Typescript在重写中的参数类型不同时认为它们在结构上是相同的,但在类型相同时(即与父级相同,IBaseModel
)一切都可以正常编译。另外,如果我的对象上已经有一个名为type
的成员,会发生什么情况?它是否与类型冲突?
?。谢谢我在这里提交了一个bug:。谢谢你的更新!是的,你的例子正是我想要做的。谢谢