运行时未定义枚举类型

78

我遇到了一个问题,就是Typescript编译器可以成功编译我的代码,但在运行时却出现了未定义类型的错误。

在我的应用程序中,我创建了一个types.ts文件,其中包含一些在多个其他ts文件之间共享的内容。 它包含一个字符串枚举,例如:

enum MyEnum {
  One = "one";
  Two = "two";
}

当我这样定义时,编译器允许我在其他ts文件中使用它,并且似乎很开心。然而,在运行时我会得到错误“未定义MyEnum”。

我知道两种解决方法:

  1. 在使用它的文件中定义枚举。但我认为这对于想要使用它的其他文件没有任何帮助。
  2. 在types.ts文件中使用“export”,并显式导入每个类型到它被使用的所有地方。

我对Typescript非常陌生,我觉得我可能误解了一些基本的东西。

首先,我不明白为什么Typescript编译器会快乐地编译我的代码,如果有运行时错误。我会理解如果我使用了“declare”关键字,告诉编译器某些内容应该在运行时可用,但在这种情况下,我不明白为什么它应该假定该枚举来自types.ts文件以外的任何地方。

其次,我想在我的应用程序中全局定义类型,并使它们在每次使用时无需导入即可使用。我该如何实现?或者这可能被视为不良实践吗?

我正在使用Typescript 2.6,我的配置如下:

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es6",
    "module": "commonjs",
    "lib": ["es6", "es7", "esnext"],

    "sourceMap": true /* Generates corresponding '.map' file. */,
    "outDir": "build" /* Redirect output structure to the directory. */,
    "removeComments": true /* Do not emit comments to output. */,

    /* Strict Type-Checking Options */
    "strict": true /* Enable all strict type-checking options. */,

    /* Additional Checks */
    "noUnusedLocals": true /* Report errors on unused locals. */,
    "noUnusedParameters": true /* Report errors on unused parameters. */,
    "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
    "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,

    "plugins": [{ "name": "tslint-language-service" }],
    "skipLibCheck": true // because firebase-sdk has wrong type files now (Nov 18)
  },
  "include": ["src/**/*"],
  "exclude": ["build"]
}

枚举将被转译为一个对象。如果您正在使用节点应用程序,则必须在要使用的文件中导出和导入它。如果不这样做,它只能在浏览器环境中工作,在该环境中,包含枚举的转译文件的脚本必须放置在引用该对象的所有其他文件之前。 - user8928802
好的,我理解这个对象的意思了。我想知道为什么这只在我的服务器代码中出现,而不是在Web客户端中。但是,您能否更详细地解释一下在浏览器中如何工作,或者提供更多信息的链接?在浏览器环境中,枚举是否成为全局声明变量? - Thijs Koerselman
不,你必须在引用它的脚本标签之前使用带有枚举对象转译的文件的脚本标签... <script src="myscriptwiththeenum.js"><script/> ... 然后是 <script src="myscriptthatusestheenum.js"><script/> ...<script src="myotherscriptthatusestheenum.js"><script/>。如果你使用webpack、browserify或其他类似的工具,只需使用import语句,就不必担心脚本顺序。 - user8928802
1
啊,好的。但从技术上讲,它仍然是全局变量,只是可能隐藏在某个地方 :) 我正在使用CRA的Typescript变体,所以我想这已经处理好了。我仍然想知道服务器端的一个好的解决方案是什么。通过使用枚举(和导出),我现在被迫也要导出和导入那个文件中存在的所有其他接口和类型声明... - Thijs Koerselman
我一直在使用/// <reference path="RelativePathToTypeScriptFile/TypeScriptFile.ts"/>来引用类和函数,并且它们都能正常工作。但是,出于某种原因,对于枚举类型却不起作用! - Matt Arnold
请忽略我之前关于 reference path 对枚举类型无效的评论;我忘记在 MVC 项目中的 BundleConfig.cs 文件中引用生成的 JavaScript 文件了。这是导致问题的原因。 - Matt Arnold
9个回答

64

在我的undefined枚举情况下,原因是循环引用:

a.ts文件中定义了export enum A {...}b.ts文件中定义了export const b = ...;

b.ts中引入了import {A} from './a.ts', 而a.ts中引入了import {b} from './b.ts'

删除循环引用后,错误消息消失了。


1
这个方法对我也起作用了。好奇,你是怎么想到这个解决方案的? - Michael Wu
哇!有人知道这是否是预期行为吗? - Ian
这个答案真的救了我的晚上。 - Stanislav E. Govorov
1
今年一直不太顺利,直到我看到了你的答案——在这个问题上纠缠了数小时——一旦看到这个答案,只用了2分钟就解决了——谢谢! - Nathan

63

还有另一种方法可以实现这一点。如果您不想导出您的枚举,您可以将其定义为一个常量枚举

const enum MyEnum {
   One = "one";
   Two = "two";
}

这些被编译器内联并在编译期间完全删除。


2
谢谢,我之前也发现了这个方法。我也用它来导出所有的枚举类型。但是我忘记了这个问题还没有被接受的答案... - Thijs Koerselman
5
如果你需要将一个字符串解析成枚举类型,要小心使用这个解决方案。因为当你使用表达式 (<any>MyEnum)[myString] === MyEnum.One 时,会出现 'const' enums can only be used in property or index access expressions or the right hand side of an import declaration or export assignment. 的错误提示。请注意不要改变原意。 - Matt Arnold
@nukefusion 啊,真令人失望;我以为使用[]语法作为.的替代方案的整个目的是允许通过变量动态访问成员。在我的情况下,最后我并不需要const,我只是忘记在我的BundleConfig.cs中包含枚举文件了。但我仍然需要<any>转换才能使它正常工作(这似乎有点像hack!)。 - Matt Arnold
3
使用 const enum 确实可行,但为什么不直接使用 enum 也能工作呢? - guijob
我已经完全停止使用枚举了。对我来说,增加的复杂性不值得。我似乎能够用字符串字面量做任何事情,而且看起来更清晰。 - Thijs Koerselman
显示剩余2条评论

18

当我导入再导出枚举时,遇到了相同的问题。这导致了运行时错误。

layout.ts

export enum Part { Column, Row }

index.ts

export * from './layout'

组件.ts

import { Part } from '../entities' // This causes error
import { Part } from '../entities/layout' // This works

1
你解决了吗?我也遇到了同样的错误,重新导出目前是唯一可行的方法,所以我有点困扰。 - user3133
1
不必使用枚举。 - sad comrade
这可能是我们经常使用这种模式的原因。我也不确定这是否与SWC解析导入/使用有关,因为它们可能被摇掉,因为它们没有直接被使用,而是在一些我们拥有的POJO中实例化,这最终会抛出一个错误,即属性在未定义的情况下不存在。我很惊讶这种情况不是更普遍,或者肯定有一个问题被提出了。我只是不确定这是否是一个特殊情况,还是与某些循环依赖或摇树有关。 - undefined

12

我遇到了这个问题,是因为我在代码中使用了"declare"

export declare enum SomeEnum

与其

export enum SomeEnum

1
你知道为什么使用 "declare" 关键字会引起这个问题吗?因为它导致我无法从一个包中进行导入。 - kmars

5

我们发现只需简单地重启应用程序即可解决问题。(Nativescript 应用程序)


1
这对我也起作用了。我创建了一个新的枚举文件,我相信应用程序尚未编译该文件,因此在React中返回未定义。 - Chima - UserAndI
也适用于我的情况。 - eddy

2

我曾经遇到过这个错误,当我使用了 export 关键字时它就消失了,即:

export enum MyEnum {
  One = "one";
  Two = "two";
}

请确保您在使用该文件的地方也导入了它,例如:

import { MyEnum } from '../types.ts';

我发现当我没有使用export关键字声明枚举时,我仍然可以在其他文件中引用该枚举而不导入它,编译器不会报错。但是只有在运行时才会抛出未定义异常。

1
谢谢,但如果您阅读问题下面的评论,您会发现我已经做到了这一点 :) 我现在主要想知道是否有办法在应用程序中全局定义类型而不必导出/导入所有内容,因为这样做对于所有类型来说真的很麻烦。我想你可以把所有枚举放在一个单独的文件中,只导出/导入那个文件。这样所有其他类型仍然是全局可用的,而且您不需要编写所有的样板代码来跨应用程序使用它们。 - Thijs Koerselman
好奇一下,你在用什么集成开发环境(IDE)?使用Visual Studio Code时,当你输入枚举的名称时,VS Code会自动为你生成导入语句,这在编码时节省了大量时间,并帮助保持流畅的连贯性。我最初使用JetBrains WebStorm,但发现在Visual Studio Code IDE中更加高效。 - Ciaran Gallagher

2

这是我的情况,当我向enum.ts文件中添加一个新的枚举时,如下所示:

enum.ts:

enum RotateMode {
  UNKNOWN = 'UNKNOWN',
  OPTIMIZE = 'OPTIMIZE',
  ROTATE_FOREVER = 'ROTATE_FOREVER'
}

export { RotateMode };

我没有再次运行tsc来编译这个文件。这导致enum.js文件没有RotateMode枚举。

enum.js:

// old file doesn't have the RotateMode enum

然后,我将RotateMode枚举导入到我的index.ts文件中:

import { RotateMode } from './enum';

console.log(`RotateMode: ${JSON.stringify(RotateMode)}`);

结果是:

这是结果。

RotateMode: undefined

不知何故,import 语法会优先从 .js 文件中导入。

因此,在添加新内容后,不要忘记编译 ts 文件


2
我在导入我的另一个项目中的枚举时遇到了类似的错误信息。我将枚举导出,但它没有声明为"const"。我在我的其他项目中更新了枚举的代码如下,并且按预期工作了。
export const enum MyEnum {
  One = 'one',
  Two = 'two'
}

0

我可能来晚了,但要注意错误堆栈指向的文件中任何@ts-ignore行。

我的问题是,在进行大规模重构后,IDE快捷方式清理了枚举的导入,并且编译器无法检测到它缺失,因为@ts-ignore注释是针对其他内容的(不幸的是,在同一行上)。


你是怎么解决的? - fsevenm

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