在if-else语句中,使用instanceof将Typescript类型缩小为never

在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

当我试图在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
不能在
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:。谢谢你的更新!是的,你的例子正是我想要做的。谢谢