请选择 进入手机版 | 继续访问电脑版

#楼主# 2020-3-5

跳转到指定楼层

基础知识

JavaScript有一种类型的具有一个有限的量的值:布尔值,它的值true和false与没有其他值。使用枚举,TypeScript允许您自己静态定义相似的类型。

数字枚举

这是一个枚举的简单示例:

enum NoYes {
  No,
  Yes, // trailing comma
}

条目No和Yes被称为枚举的成员NoYes。与对象文字一样,尾随逗号是允许和忽略的。

我们可以利用成员,如果他们的文字,例如true,123或者'abc'-例如:

function toGerman(value: NoYes) {
  switch (value) {
    case NoYes.No:
      return 'Nein';
    case NoYes.Yes:
      return 'Ja';
  }
}
assert.equal(toGerman(NoYes.No), 'Nein');
assert.equal(toGerman(NoYes.Yes), 'Ja');

枚举成员值

每个枚举成员都有一个名称和一个值。枚举的默认值是数字。也就是说,每个成员值都是一个数字:

enum NoYes {
  No,
  Yes,
}
assert.equal(NoYes.No, 0);
assert.equal(NoYes.Yes, 1);
除了TypeScript为我们指定枚举成员值之外,我们还可以自己指定它们:

enum NoYes {
  No = 0,
  Yes = 1,
}

这种通过等号的显式指定称为初始化程序。

如果前面的成员值是数字,则可以省略成员的值。然后,TypeScript将该值加1,并将其用于当前成员:

enum Enum {
  A,
  B,
  C = 4,
  D,
  E = 8,
  F,
}
assert.deepEqual(
  [Enum.A, Enum.B, Enum.C, Enum.D, Enum.E, Enum.F],
  [0, 1, 4, 5, 8, 9]
);

的枚举成员名称套管

常量的命名有几个先例(在枚举或其他地方):

传统上,JavaScript使用全大写的名称,这是它从Java和C继承的约定: Number.MAX_VALUE
众所周知的符号用驼峰式表示,并以小写字母开头,因为它们与属性名称相关: Symbol.asyncIterator
TypeScript手册使用以大写字母开头的驼峰式名称。这是标准的TypeScript样式,我们将其用于NoYes枚举。

引用枚举成员名称

与JavaScript对象类似,我们可以引用枚举成员的名称:

enum HttpRequestField {
  'Accept',
  'Accept-Charset',
  'Accept-Datetime',
  'Accept-Encoding',
  'Accept-Language',
}
assert.equal(HttpRequestField['Accept-Charset'], 1);

无法计算枚举成员的名称。对象文字通过方括号支持计算的名称。

基于字符串的枚举

除了数字,我们还可以使用字符串作为枚举成员值:

enum NoYes {
  No = 'No',
  Yes = 'Yes',
}
assert.equal(NoYes.No, 'No');
assert.equal(NoYes.Yes, 'Yes');

如果枚举完全基于字符串,则我们不能省略任何初始化程序。

异构枚举

最后一种枚举称为异质枚举。异构枚举的成员值是数字和字符串的混合:

enum Enum {
  A,
  B,
  C = 'C',
  D = 'D',
  E = 8,
  F,
}
assert.deepEqual(
  [Enum.A, Enum.B, Enum.C, Enum.D, Enum.E, Enum.F],
  [0, 1, 'C', 'D', 8, 9]
);

请注意,前面提到的规则也适用于此:如果先前的成员值为数字,则我们只能省略初始化程序。

异构枚举由于其应用很少而很少使用。

Type,TypeScript仅支持将数字和字符串作为枚举成员值。不允许使用其他值,例如符号。

指定枚举成员值

TypeScript区分了三种指定枚举成员值的方式:

文字枚举成员已初始化:

隐含地或
通过数字文字或字符串文字(显式)。到目前为止,我们仅使用文字成员。
常量枚举成员通过可在编译时计算其结果的表达式初始化。

计算的枚举成员可通过任意表达式初始化。

在此列表中,较早的条目不太灵活,但支持更多功能。接下来的小节将更详细地介绍每个条目。

文字枚举成员

如果指定了枚举成员的值,则它是文字的:

隐含地或
通过数字文字(包括否定的数字文字)或
通过字符串文字。
如果枚举只有文字成员,我们可以将这些成员用作类型(类似于数字文字可以用作类型的方式):

enum NoYes {
  No = 'No',
  Yes = 'Yes',
}
function func(x: NoYes.No) {
  return x;
}

func(NoYes.No); // OK

//@ts-ignore: Argument of type '"No"' is not assignable to
//            parameter of type 'NoYes.No'.
func('No');

//@ts-ignore: Argument of type 'NoYes.Yes' is not assignable to
//            parameter of type 'NoYes.No'.
func(NoYes.Yes);

此外,文字枚举支持穷举检查(我们将在后面进行介绍)。

恒枚举成员

如果可以在编译时计算枚举成员的值,则该常量是常量。因此,我们可以隐式指定其值(即,让TypeScript为我们指定它的值)。或者我们可以显式指定它,并且仅允许使用以下语法:

数字文字或字符串文字
对先前定义的常量枚举成员的引用(在当前枚举中或在先前枚举中)
括号
一元运算符+,-,~
二进制运算符+,-,*,/,%,<<,>>,>>>,&,|,^
这是一个其成员都是常量的枚举的示例(我们将很快看到该枚举的用法):

enum Perm {
  UserRead     = 1 << 8, // bit 8
  UserWrite    = 1 << 7,
  UserExecute  = 1 << 6,
  GroupRead    = 1 << 5,
  GroupWrite   = 1 << 4,
  GroupExecute = 1 << 3,
  AllRead      = 1 << 2,
  AllWrite     = 1 << 1,
  AllExecute   = 1 << 0,
}

如果枚举仅具有常量成员,则不能再将成员用作类型。但是我们仍然可以进行详尽的检查。

计算枚举成员

可以通过任意表达式指定计算枚举成员的值。例如:

enum NoYesNum {
  No = 123,
  Yes = Math.random(), // OK
}

这是一个数字枚举。基于字符串的枚举和异构枚举受到更多限制。例如,我们不能使用方法调用来指定成员值:

enum NoYesStr {
  No = 'No',
  //@ts-ignore: Computed values are not permitted in
  //            an enum with string valued members.
  Yes = ['Y', 'e', 's'].join(''),
}

数字枚举的缺点

缺点:记录

在记录数字枚举的成员时,我们只会看到数字:

enum NoYes { No, Yes }

console.log(NoYes.No);
console.log(NoYes.Yes);

// Output:
// 0
// 1

缺点:松散型检查

将枚举用作类型时,静态允许的值不只是枚举成员的值–可以接受任何数字:

enum NoYes { No, Yes }
function func(noYes: NoYes) {}
func(33); // no error!

为什么没有更严格的静态检查?Daniel Rosenwasser解释:

该行为是由按位运算引起的。有时SomeFlag.Foo | SomeFlag.Bar打算产生另一种SomeFlag。相反,您最终得到了number,并且您不想回退到SomeFlag。

我认为,如果我们再次进行TypeScript并仍然有枚举,那么我们将为位标志建立一个单独的构造。

枚举如何用于位模式将在稍后详细介绍。

建议:喜欢基于字符串的枚举

我的建议是偏爱基于字符串的枚举(为简便起见,此博客文章并不总是遵循此建议):

enum NoYes { No='No', Yes='Yes' }

一方面,日志输出对人类更有用:

console.log(NoYes.No);
console.log(NoYes.Yes);

// Output:
// 'No'
// 'Yes'
另一方面,我们得到更严格的类型检查:

function func(noYes: NoYes) {}

//@ts-ignore: Argument of type '"abc"' is not assignable
//            to parameter of type 'NoYes'.
func('abc');
//@ts-ignore: Argument of type '"Yes"' is not assignable
//            to parameter of type 'NoYes'.
func('Yes');

用例的枚举

用例:位模式

在Node.js文件系统模块中,几个函数具有参数模式。它的值用于通过Unix保留的编码来指定文件权限:

为三类用户指定了权限:
用户:文件的所有者
组:与文件关联的组的成员
全部:所有人
对于每个类别,可以授予以下权限:
r(读取):允许类别中的用户读取文件
w(写):允许类别中的用户更改文件
x(执行):允许类别中的用户运行文件
这意味着权限可以用9位表示(3个类别,每个类别具有3个权限):

用户 组 所有
权限 r,w,x r,w,x r,w,x
位 8、7、6 5 4 3 2 1 0
Node.js不会这样做,但是我们可以使用一个枚举来处理这些标志:

权限r,w,xr,w,xr,w,x
8、7、65 4 32 1 0
enum Perm {
  UserRead     = 1 << 8, // bit 8
  UserWrite    = 1 << 7,
  UserExecute  = 1 << 6,
  GroupRead    = 1 << 5,
  GroupWrite   = 1 << 4,
  GroupExecute = 1 << 3,
  AllRead      = 1 << 2,
  AllWrite     = 1 << 1,
  AllExecute   = 1 << 0,
}

位模式通过按位Or组合:

// User can change, read and execute; everyone else can only read and execute
assert.equal(
  Perm.UserRead | Perm.UserWrite | Perm.UserExecute |
  Perm.GroupRead | Perm.GroupExecute |
  Perm.AllRead | Perm.AllExecute,
  0o755);

// User can read and write; group members can read; everyone can’t access at all.
assert.equal(
  Perm.UserRead | Perm.UserWrite | Perm.GroupRead,
  0o640);

对位模式的替代

位模式背后的主要思想是存在一组标志,并且可以选择这些标志的任何子集。

因此,使用实集选择子集是执行同一任务的一种更具描述性的方式:

enum Perm {
  UserRead,
  UserWrite,
  UserExecute,
  GroupRead,
  GroupWrite,
  GroupExecute,
  AllRead,
  AllWrite,
  AllExecute,
}
function writeFileSync(
  thePath: string, permissions: Set<Perm>, content: string) {
  // ···
}
writeFileSync(
  '/tmp/hello.txt',
  new Set([Perm.UserRead, Perm.UserWrite, Perm.GroupRead]),
  'Hello!');

用例:多个常量

有时,我们有一组属于在一起的常量:

// Log level:
const off = Symbol('off');
const info = Symbol('info');
const warn = Symbol('warn');
const error = Symbol('error');
这是一个很好的枚举用例:

enum LogLevel {
  off = 'off',
  info = 'info',
  warn = 'warn',
  error = 'error',
}

该枚举的好处是:

常量名称被分组并嵌套在名称空间内LogLevel。
LogLevel只要需要这些常量之一,就可以使用类型,并且TypeScript静态检查是否不使用其他值。

用例:更自我描述比布尔

当使用布尔值表示替代方案时,枚举通常是一种更具自我描述性的选择。

布尔型示例:有序列表与无序列表

例如,为了表示列表是否有序,我们可以使用布尔值:

class List1 {
  isOrdered: boolean;
  // ···
}

但是,枚举更具有自我描述性,并具有其他好处,即如果需要,我们可以在以后添加更多替代项。

enum ListKind { ordered, unordered }
class List2 {
  listKind: ListKind;
  // ···
}

布尔型示例:失败与成功

同样,我们可以通过布尔值或枚举来编码操作是成功还是失败:

class Result1 {
  success: boolean;
  // ···
}

enum ResultStatus { failure, success }
class Result2 {
  status: ResultStatus;
  // ···
}

用例:更安全的字符串常量

考虑以下创建正则表达式的函数。

const GLOBAL = 'g';
const NOT_GLOBAL = '';
type Globalness = typeof GLOBAL | typeof NOT_GLOBAL;

function createRegExp(source: string,
  globalness: Globalness = NOT_GLOBAL) {
    return new RegExp(source, 'u' + globalness);
  }

assert.deepEqual(
  createRegExp('abc', GLOBAL),
  /abc/ug);

使用基于字符串的枚举更为方便:

enum Globalness {
  Global = 'g',
  notGlobal = '',
}

function createRegExp(source: string, globalness = Globalness.notGlobal) {
  return new RegExp(source, 'u' + globalness);
}

assert.deepEqual(
  createRegExp('abc', Globalness.Global),
  /abc/ug);

在运行时枚举

TypeScript将枚举编译为JavaScript对象。例如,使用以下枚举:

enum NoYes {
  No,
  Yes,
}
TypeScript将该枚举编译为:

var NoYes;
(function (NoYes) {
  NoYes[NoYes["No"] = 0] = "No";
  NoYes[NoYes["Yes"] = 1] = "Yes";
})(NoYes || (NoYes = {}));
在此代码中,进行了以下分配:

NoYes["No"] = 0;
NoYes["Yes"] = 1;

NoYes[0] = "No";
NoYes[1] = "Yes";

有两组练习:

前两个分配将枚举成员名称映射到值。
后两个赋值将值映射到名称。这将启用反向映射,我们将在后面介绍。
反向映射
给定一个数字枚举:

enum NoYes {
  No,
  Yes,
}

普通的映射是从成员名称到成员值:

// Static (= fixed) lookup:
assert.equal(NoYes.Yes, 1);

// Dynamic lookup:
assert.equal(NoYes['Yes'], 1);

数值枚举还支持从成员值到成员名称的反向映射:

assert.equal(NoYes[1], 'Yes');

在运行时基于字符串的枚举
基于字符串的枚举在运行时具有更简单的表示形式。

考虑以下枚举。

enum NoYes {
  No = 'NO!',
  Yes = 'YES!',
}

它被编译为以下JavaScript代码:

var NoYes;
(function (NoYes) {
    NoYes["No"] = "NO!";
    NoYes["Yes"] = "YES!";
})(NoYes || (NoYes = {}));

TypeScript不支持基于字符串的枚举的反向映射。

const枚举

如果枚举以关键字为前缀const,则在运行时没有任何表示形式。而是直接使用其成员的值。

编译非const枚举

为了观察这种效果,让我们首先检查以下非常量枚举:

enum NoYes {
  No,
  Yes,
}
function toGerman(value: NoYes) {
  switch (value) {
    case NoYes.No:
      return 'Nein';
    case NoYes.Yes:
      return 'Ja';
  }
}

TypeScript将此代码编译为:

var NoYes;
(function (NoYes) {
  NoYes[NoYes["No"] = 0] = "No";
  NoYes[NoYes["Yes"] = 1] = "Yes";
})(NoYes || (NoYes = {}));

function toGerman(value) {
  switch (value) {
    case NoYes.No:
      return 'Nein';
    case NoYes.Yes:
      return 'Ja';
  }
}

编译常量枚举

这与以前的代码相同,但是现在的枚举是const:

const enum NoYes {
  No,
  Yes,
}
function toGerman(value: NoYes) {
  switch (value) {
    case NoYes.No:
      return 'Nein';
    case NoYes.Yes:
      return 'Ja';
  }
}

现在,枚举作为构造的表示形式消失了,仅保留了其成员的值:

function toGerman(value) {
  switch (value) {
    case 0 /* No */:
      return 'Nein';
    case 1 /* Yes */:
      return 'Ja';
  }
}

在编译时枚举

枚举是对象

TypeScript将(非const)枚举视为对象:

enum NoYes {
  No = 'No',
  Yes = 'Yes',
}
function func(obj: { No: string }) {
  return obj.No;
}
assert.equal(
  func(NoYes), // allowed statically!
  'No');

字面枚举全面性检查

当我们接受一个枚举成员值时,我们通常要确保:

我们没有收到非法价值。
我们没有忘记考虑任何枚举成员值。(如果以后再添加新的枚举成员值,则这一点尤为重要。)

抵御非法值

在以下代码中,我们针对非法值采取了两种措施:

enum NoYes {
  No = 'No',
  Yes = 'Yes',
}

function toGerman(value: NoYes) {
  switch (value) {
    case NoYes.No:
      return 'Nein';
    case NoYes.Yes:
      return 'Ja';
    default:
      throw new TypeError('Unsupported value: ' + JSON.stringify(value));
  }
}

assert.throws(
  //@ts-ignore: Argument of type '"Maybe"' is not assignable to
  //            parameter of type 'NoYes'.
  () => toGerman('Maybe'),
  /^TypeError: Unsupported value: "Maybe"$/);

这些措施是:

在编译时,该类型NoYes可防止将非法值传递给parameter value。
在运行时,default如果有意外值,则使用case引发异常。
通过全面性检查抵御遗忘案件
我们可以再采取一种措施。以下代码执行详尽检查:如果我们忘记考虑所有枚举成员,TypeScript将警告我们。

enum NoYes {
  No = 'No',
  Yes = 'Yes',
}

function throwUnsupportedValue(value: never): never {
  throw new TypeError('Unsupported value: ' + value);
}

function toGerman2(value: NoYes) {
  switch (value) {
    case NoYes.No:
      return 'Nein';
    case NoYes.Yes:
      return 'Ja';
    default:
      throwUnsupportedValue(value);
  }
}
详尽性检查如何工作?对于每种情况,TypeScript都会推断value:

function toGerman2b(value: NoYes) {
  switch (value) {
    case NoYes.No:
      const x: NoYes.No = value;
      return 'Nein';
    case NoYes.Yes:
      const y: NoYes.Yes = value;
      return 'Ja';
    default:
      const z: never = value;
      throwUnsupportedValue(value);
  }
}

在默认情况下,TypeScript会never为其推断类型,value因为我们从没有到达那里。但是,如果我们添加一个成员.Maybe到NoYes,然后推断类型value是NoYes.Maybe。并且该类型与never的参数类型在静态上不兼容throwUnsupportedValue()。因此,我们在编译时会收到以下错误消息:

'NoYes.Maybe'类型的参数不能分配给'never'类型的参数。

方便的是,这种详尽检查也适用于以下if语句:

function toGerman3(value: NoYes) {
  if (value === NoYes.No) {
    return 'Nein';
  } else if (value === NoYes.Yes) {
    return 'Ja';
  } else {
    throwUnsupportedValue(value);
  }
}

检查全面性的另一种方法

另外,如果我们为以下项指定返回类型,我们还将得到详尽的检查toGerman():

enum NoYes {
  No = 'No',
  Yes = 'Yes',
}
function toGerman(value: NoYes): string {
  switch (value) {
    case NoYes.No:
      const x: NoYes.No = value;
      return 'Nein';
    case NoYes.Yes:
      const y: NoYes.Yes = value;
      return 'Ja';
  }
}

如果我们向中添加成员NoYes,则TypeScript抱怨toGerman()可能会返回undefined。

这种方法的缺点: las,这种方法不适用于if语句(更多信息)。

keyof和枚举
我们可以使用keyof类型运算符创建类型,其元素是枚举成员的键。当我们这样做,我们需要结合keyof使用typeof:

enum HttpRequestKeyEnum {
  'Accept',
  'Accept-Charset',
  'Accept-Datetime',
  'Accept-Encoding',
  'Accept-Language',
}
type HttpRequestKey = keyof typeof HttpRequestKeyEnum;
  // = 'Accept' | 'Accept-Charset' | 'Accept-Datetime' |
  //   'Accept-Encoding' | 'Accept-Language'

function getRequestHeaderValue(request: Request, key: HttpRequestKey) {
  // ···
}

为什么这样 比HttpRequestKey直接定义类型更方便。

keyof不使用typeof
如果使用keyof不使用typeof,则会得到另一个不太有用的类型:

type Keys = keyof HttpRequestKeyEnum;
  // = 'toString' | 'toFixed' | 'toExponential' |
  //   'toPrecision' | 'valueOf' | 'toLocaleString'

keyof HttpRequestKeyEnum与相同keyof number。

转播转播
回复

使用道具

99

主题

101

帖子

418

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
418
admin 发表于 2020-3-5 19:41:50

顶上

回复

使用道具 举报

99

主题

101

帖子

418

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
418
admin 发表于 2020-3-24 19:45:11

非常好

回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于作者

admin

管理员

  • 主题

    99

  • 帖子

    101

  • 关注者

    0

手机版|ObjectX 超对象 |粤ICP备20005929号
Powered by  © 2019-2020版权归ObjectX 超对象