为什么在TypeScript中使用'instanceof'时会出现错误“'Foo'只能作为类型引用,但在此处被用作值。”?

239

我写了这段代码

interface Foo {
    abcdef: number;
}

let x: Foo | string;

if (x instanceof Foo) {
    // ...
}

但 TypeScript 给我抛出了这个错误:

'Foo' only refers to a type, but is being used as a value here.

为什么会发生这种情况?我以为 instanceof 可以检查我的值是否具有给定的类型,但 TypeScript 似乎不喜欢这样。


请参考@4castle的答案。否则,你是正确的,我会将其改为Foo | string - Daniel Rosenwasser
可能是在TypeScript联合类型中检查变量是否为特定接口类型的重复问题(我不想独自解决这个问题)。 - Cerbrus
1
@Jenny O'Reilly,这绝对是一个可能的重复! - marckassay
6个回答

204

简述

instanceof 只适用于类,而不适用于接口和类型别名。


TypeScript 试图告诉我什么?

问题在于 instanceof 是 JavaScript 中的一个构造,而在 JavaScript 中,instanceof 需要右侧操作数传递一个 。 具体来说,在 x instanceof Foo 中,JavaScript 将在原型链中检查是否存在 Foo.prototype

然而,在 TypeScript 中,interface 没有发出。同样适用于 type 别名。这意味着在运行时既不存在 Foo 也不存在 Foo.prototype,因此此代码肯定会失败。

TypeScript 告诉你这永远不可能起作用。 Foo 只是一种类型,根本不是值!

如果您来自另一种语言,则可能希望在此处使用类。类确实会在运行时创建值,但您可能想阅读下面的一些注释。

"如果我仍然需要 typeinterface,除了 instanceof,我该怎么办?"

您可以查看 type guards and user-defined type guards

"但如果我只是从一个接口切换到一个呢?"

你可能会想要从一个接口切换到一个,但你应该意识到,在TypeScript的结构类型系统中(其中事物主要基于形状),你可以创建任何与给定类具有相同形状的对象:

class C {
    a: number = 10;
    b: boolean = true;
    c: string = "hello";
}

let x = new C()
let y: C = {
    a: 10, b: true, c: "hello",
}

// Works!
x = y;
y = x;

在这种情况下,您有具有相同类型的“x”和“y”,但是如果尝试在任何一个上使用“instanceof”,则另一个会返回相反的结果。因此,如果利用 TypeScript 中的结构类型,那么“instanceof”实际上并不会告诉您有关类型的太多信息。

所以基本上我没有从答案中得到应该选择哪个更好的想法。类?因为你详细说明了它。但同时也感到困惑,因为你提到了“你可能会被诱惑”。那么,如果我需要比较所有属性而不仅仅是类型保护文档中的游泳属性,该怎么办? - HalfWebDev
37
这里的主要观点是 instanceof 只适用于类,而不是接口。需要强调这一点。 - inorganik

28

如果您希望检查具有不同属性/函数的接口,可以使用类型保护在运行时进行类型检查。

示例

let pet = getSmallPet();

if ((pet as Fish).swim) {
    (pet as Fish).swim();
} else if ((pet as Bird).fly) {
    (pet as Bird).fly();
}

如果我学习了关于鸭子的知识,并将swim()函数添加到我的Bird接口中,那么在类型保护中,每个宠物都不会被归类为鱼吗?如果我有三个接口,每个接口有三个函数,并且其中两个与另一个接口重叠,该怎么办? - Kayz
1
@Kayz 如果您没有唯一标识接口的属性/函数,那么您无法真正区分它们。您的宠物可能实际上是一只“鸭子”,您可以使用类型保护将其变成“鱼”,但是在调用“swim()”时仍然不会出现运行时异常。建议您创建一个公共接口层级(例如“Swimmable”),并将您的“swim()”函数移动到该层级中,然后类型保护仍然可以与((pet as Swimmable).swim)很好地配合使用。 - Lee Chee Kiam
1
为了防止类型转换,您可以使用'swim' in pet条件。它将缩小到必须定义swim的子集(例如:Fish | Mammal)。 - Akxe

3

虽然Daniel Rosenwasser的回答可能是正确的,但我感觉需要对他的回答进行修改。可以完全检查x的实例,如代码片段所示。

但同样容易将x = y赋值。现在,x将不是C的实例,因为y只具有C的形状。

class C {
a: number = 10;
b: boolean = true;
c: string = "hello";
}

let x = new C()
let y = {
    a: 10, b: true, c: "hello",
}

console.log('x is C? ' + (x instanceof C)) // return true
console.log('y is C? ' + (y instanceof C)) // return false

1

0
你可以使用in运算符缩小范围来检查你需要的元素是否在对象中。
通过这种方法,你可以验证x是一个字符串还是Foo。
if ('abcdef' in x) {
    // x is instance of Foo
}

0

instanceof通常是正确的答案。

然而,在我的情况下,我正在使用一个原始类型,instanceof Numberinstanceof number都在ts编译器中出现错误:

TS2322: Type 'Number' is not assignable to type 'number'.

TS2693: 'number' only refers to a type, but is being used as a value here.

typeof 是在使用原始类型时的正确答案:

if (typeof value === 'number') {this.propertyValue = value}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接