TypeScript 中的字符串枚举

Typescript 2.4 实现了一个呼声很高的特性:字符串枚举,或者更准确地说,成员的值是 string 类型的枚举。

现在允许将一个字符串赋值给一个枚举成员:

enum MediaTypes {
  JSON = 'application/json',
  XML = 'application/xml',
}

字符串枚举可以像其他 Typescript 中的枚举一样被使用:

enum MediaTypes {
  JSON = 'application/json',
  XML = 'application/xml',
}

fetch('https://example.com/api/endpoint', {
  headers: {
    Accept: MediaTypes.JSON,
  },
}).then((response) => {
  // ...
})

下面是编译器生成的 ES3/ES5 代码:

var MediaTypes
;(function (MediaTypes) {
  MediaTypes['JSON'] = 'application/json'
  MediaTypes['XML'] = 'application/xml'
})(MediaTypes || (MediaTypes = {}))
fetch('https://example.com/api/endpoint', {
  headers: {
    Accept: MediaTypes.JSON,
  },
}).then(function (response) {
  // ...
})

输出的结果和数字成员类型的枚举编译输出的几乎一样,但字符串成员的枚举没有反向的映射。

字符串枚举成员没有反向映射

对于每一个枚举,Typescript 都会生成一些映射代码构造出一个映射对象。但对于字符串枚举成员,这个映射对象只定义了 key 到 value 的映射,没有反向的映射。

var MediaTypes
;(function (MediaTypes) {
  MediaTypes['JSON'] = 'application/json'
  MediaTypes['XML'] = 'application/xml'
})(MediaTypes || (MediaTypes = {}))

这意味着,我们可以通过 key 来得到 value,但不能通过 value 得到它的 key:

MediaTypes['JSON'] // "application/json"
MediaTypes['application/json'] // undefined

MediaTypes['XML'] // "application/xml"
MediaTypes['application/xml'] // undefined

让我们来对比下数字类型成员的枚举:

enum DefaultPorts {
  HTTP = 80,
  HTTPS = 443,
}

在这个例子中,编译器额外生成了一个 value 到 key 的反向映射:

var DefaultPorts
;(function (DefaultPorts) {
  DefaultPorts[(DefaultPorts['HTTP'] = 80)] = 'HTTP'
  DefaultPorts[(DefaultPorts['HTTPS'] = 443)] = 'HTTPS'
})(DefaultPorts || (DefaultPorts = {}))

这个反向映射允许我们既可以通过 value 获得 key,也可以通过 key 获得 value。

DefaultPorts['HTTP'] // 80
DefaultPorts[80] // "HTTP"

DefaultPorts['HTTPS'] // 443
DefaultPorts[443] // "HTTPS"

通过常量枚举(const enum)来内联枚举的成员

为了避免生成枚举映射代码带来的开销,我们可以将 MediaTypes 枚举转换成一个常量枚举,只需要在声明的时候加上 const 修饰符:

const enum MediaTypes {
  JSON = 'application/json',
  XML = 'application/xml',
}

fetch('https://example.com/api/endpoint', {
  headers: {
    Accept: MediaTypes.JSON,
  },
}).then((response) => {
  // ...
})

加了 const 之后,编译器不会再为我们的 MediaTypes 生成任何的映射代码。相反的,它会在所有使用枚举成员的地方内联具体的值,这样减少了一些代码以及避免属性访问的开销。

fetch('https://example.com/api/endpoint', {
  headers: {
    Accept: 'application/json' /* JSON */,
  },
}).then(function (response) {
  // ...
})

但是,出于某些原因,我们想在运行时访问这个映射对象怎么办?

通过 preserveConstEnums 生成常量枚举代码

有些时候,为一个常量枚举生成映射代码是必要的,比如有一些 Javascript 代码需要访问它的时候。对于这种场景,你可以在 tsconfig.json 文件中开启 preserveConstEnums 编译选项。

{
  "compilerOptions": {
    "target": "es5",
    "preserveConstEnums": true
  }
}

在设置 preserveConstEnums 之后,再次编译我们的代码,编译器依然会內联 MediaTypes.JSON 的值,但是它同时会生成映射代码:

var MediaTypes
;(function (MediaTypes) {
  MediaTypes['JSON'] = 'application/json'
  MediaTypes['XML'] = 'application/xml'
})(MediaTypes || (MediaTypes = {}))
fetch('https://example.com/api/endpoint', {
  headers: {
    Accept: 'application/json' /* JSON */,
  },
}).then(function (response) {
  // ...
})