TypeScript基础React

typescript 枚举

2019-01-19  本文已影响116人  一慢呀
枚举的基础
// 利用 const 关键词也可以声明一组常量,例如,声明十二生肖的排位
const rat: number = 1
const cattle: number = 2
const tiger: number = 3
const rabbit: number = 4
const dragon: number = 5
// 上述只声明了 5 个,如果声明全排位,需要声明 12 个变量,并且注明
// 类型,但是却多了很多重复性工作,利用数字枚举,我们可以轻松声明同样的一组常量
enum ChineseZodiac {
  rat,
  cattle,
  tiger,
  rabbit,
  dragon
}
// 如果想访问 dragon 生肖,只需要像 js 中对象使用点语法或者中括号访问对象成员即可
ChineseZodiac.dragon === ChineseZodiac['dragon'] => true
// 枚举使用 enum 关键词来声明一个枚举,数字枚举和字符串枚举的区别
// 就是枚举成员是常数还是字符串;还是以十二生肖为例:
// 01-数字枚举,声明了一个枚举成员均不带初始化器的枚举,
// 该枚举的成员值从 0 开始,依次递增
enum NumChineseZodiac {
  rat,
  cattle,
  tiger,
  rabbit,
  dragon
}
// 02-字符串枚举
enum StrChineseZodiac {
  rat = 'rat',
  cattle = 'cattle',
  tiger = 'tiger',
  rabbit = 'rabbit',
  dragon = 'dragon'
}
// 关于两者之间细微的区别,下面会说到
枚举的基本分类
数字枚举
// 数字枚举的声明可以分为两大类,带有初始化器和不带初始化器
// 01-不带初始化器,枚举成员默认从 0 开始,依次递增;
enum NumEnum1 { one, two }
NumEnum1.one => 0
NumEnum1.two => 1
// 02-带有初始化器,这种又可以分为两种:
// 02-01-使用初始化器并指定初始化的常数,
// 未使用初始化器的成员取值是在上一个成员的基础上 +1;
enum NumEnum2 {
  one = 10,
  two,
  three = 20,
  four
}
NumEnum2.two => 11
NumEnum2.four => 21
// 02-02-使用初始化器并且初始化值是对已经声明的枚举的枚举成员的引用
enum NumEnum3 {
  one = NumEnum2.four,
  two
}
NumEnum3.one => 21
NumEnum3.two => 22
enum NumEnum { one, two }
NumEnum.one => 0
NumEnum[1] => 'two'
// 其实这个不难理解,编译的过程就像官网举的栗子:
var NumEnum;
(function (NumEnum) {
    NumEnum[NumEnum["one"] = 0] = "one";
    NumEnum[NumEnum["two"] = 1] = "two";
})(NumEnum || (NumEnum = {}));
// 对象支持以 number 类型的数据作为键
// 原因就是 number 类型会被转为 string 类型,但是有些却不适合,往下看;
// 枚举成员不是变量,而是常数,因此枚举成员又称为枚举常量
// 因为是常量,所以不能对枚举成员进行赋值,以下声明的枚举成员均为常量
// 01-枚举成员不使用任何形式的初始化器
enum NumEnum1 { one, two }

// 02-枚举成员不带初始化器,但是它前一个枚举成员使用了常数来初始化:
// two 未使用初始化器,但是它之前的成员 one 使用了常数赋值,
// 所以,two、three 均为常量,并且以 10 为基础递增;
enum NumEnum2 { one = 10, two, three }
// 你可能想到下面的方式来初始化,按逻辑来理解,没有问题,但是发现编译器报错了
// 是因为枚举成员初始化的时候使用的并非是常量枚举表达式,导致枚举声明错误
// 也就是说,枚举成员要么不使用初始器,要么使用常数初始器,要么使用常量枚举表达式,否则均会失败
const first = 10
enum NumEnum2 { one = first, two, three }

// 03-使用常量枚举表达式
// 03-01-字面量方式,就像前面不带初始化器声明的枚举
enum NumEnum301 { one = 1 }

// 03-02-对之前定义的常量枚举成员的引用
// 下面的枚举第一个成员 one 是对上一个枚举成员的引用,第二个成员 two 是对当前枚举成员的的引用
enum NumEnum302 { one = NumEnum301.one, two = 2 * one }

// 03-03-带括号的常量枚举表达式:
// 关于对这块的个人的理解,即可以是函数的调用,也可以是计算表达式的求值
function returnNumber (x: number): number {
  return x
}
enum NumEnum303 {
  one = (function () { return 1 })(),
  two = returnNumber(10),
  three = (NumEnum2.one + NumEnum2.two) % NumEnum2.three
}

// 03-04-一元运算符 +, -, ~ 其中之一应用在了常量枚举表达式
enum NumEnum304 {
  // 取反运算
  one = ~NumEnum2.one,
  two = +NumEnum2.two,
  three = -NumEnum2.three,
  four = void 0
}

// 03-05-二元运算符 +、-、*、/、%、
// 位运算符: <<(左移运算符)、>>(有符号右移运算符)、>>>(无符号右移运算符)、&, |, ^ 应用在了常量枚举表达式中;
enum NumEnum305 {
  // 左移运算
  one = 2 << 5,
  // 右移运算
  two = 64 >> 5,
  three = 64 >>> 5,
  // 或 运算合并 乘性运算
  four = (one | two) * three
}

// 03-06-枚举成员也可以是经过计算得来的
// 这个计算并非是运算符或者函数的处理结果,类似计算属性
enum StrEnum {
  one = 'one'
}
enum NumEnum306 {
  one = StrEnum.one.length,
  three = 'three'.length
}

// 03-07-枚举表达式求值结果为 NaN 或者 Infinity,在编译阶段会抛错,下面详解原因
// 异构枚举
enum MixinsEnum {
  a = 1,
  b = 'b'
}
// 数字枚举
enum NumEnum {
  // 会发现编辑器提示错误,因为运算符的右侧并非期望的 number 或者 enum 类型
  a = MixinsEnum.a * MixinsEnum.b
}

上面我们说到了,数字枚举成员不是变量,是一个常数,可以理解成为别名,并且数字枚举会生成反向映射,值键对形式中,keynumber,虽然会被转为 string 类型;
NaN: not a number,即不是一个数字,在W3C关于NaN的介绍中提到,NaN 不是常量,虽然它的 typenumber,所以在 ts 的数字枚举系统中不可作为枚举成员的值,同样的,Infinity 表示的是正无穷大的数值,并非一个常数,所以枚举表达式求值结果返回 Infinity 也会报错

字符串枚举
// 全部使用字符串字面量来初始化
enum StrEnum1 {
  one = 'one',
  two = 'two'
}

// 全部使用其他枚举成员的字面量初始化,
// 当然不仅限于 StrEnum1 枚举,也可以是其他字符串枚举
enum StrEnum2 {
  one = StrEnum.one,
  two = StrEnum.two
}

// 但是不可将这两种方式初始化方式混写
enum StrEnum3 {
  // 编辑器会报错
  one = 'first',
  // 采用计算属性的枚举表达式,ts 会认为你在初始化一个数字枚举,
  // 但是如果当前枚举含有字符串枚举成员,这回形成矛盾,所以会报错;
  two = StrEnum.two
  // 当然你可以手动指定常数枚举成员,从而避开这种校验;
  // ok
  three = 3
}
// 你可能会对上述第二种初始化枚举有疑问,为什么均使用其他枚举引用不会有问题
// 这是因为 ts 不会校验引用枚举成员
// 如果都为常数,那么就是数字枚举,如果都为字符串,就是字符串枚举,否则就是异构枚举。
// 看这个栗子
enum StrEnum {
  one = 'one',
  two = 'two'
}
// 会发现编译之后的并没有像数字枚举生成方向映射
var StrEnum;
(function (StrEnum) {
  StrEnum["one"] = "one";
  StrEnum["two"] = "two";
})(StrEnum || (StrEnum = {}));
// 上述栗子比较极端,但很容易明白,如果生成值键对,将毫无意义
// 因为会覆盖掉,归根结底还是因为字符串赋值的不确定性。
运行时的枚举
// 一个十二生肖中前五个的排位
enum ChineseZodiac { rat = 1, cattle, tiger, rabbit, dragon }

// 读取某一生肖的排位或者根据某一排位查找生肖名称
function getChineseZodiac(zodiac: { tiger: number }): void {
  console.log(zodiac.tiger) // => tiger 是第三位
  console.log(zodiac[5])    // => 第五位的是 dragon
}
getChineseZodiac(ChineseZodiac)
枚举的进阶

枚举按照枚举成员可以分为数字、字符串、异构(混入)三大类,上面都介绍过了;枚举按照声明方式可分为四种,下面依次介绍

// 关键词 + 枚举名称
enum NumEnum { a, b c }
// 修饰符 + 关键词 + 枚举名称
const enum NumEnum { a, b, c }
// 上述声明了一个常量枚举,并且内部的数据均为只读常量
// const 枚举不会生成 lookup table,并且运行时不可访问当前枚举对象,只允许访问枚举成员的值;
const OBJ = NumEnum => Error,运行时不存在
function returnNumber (obj: { a: number }): number {
  return obj.a
}
returnNumber(NumEnum) => Error,运行时不存在
const A = NumEnum['0'] => Error,没有生成反向映射,所以不存在该属性
const B = NumEnum['b'] => 0/* b */
// 基于 const 枚举的特点,如果你只是为了生成一组常量并且只需要获取某一个常量
// 从性能方面考虑,const 枚举是首选;
// 当然如果你在 tsconfig.json 中指定下面选项
"compilerOptions": {
  // 保留 const 和 enum 声明该项为 true
  "preserveConstEnums": true
}
// 或者在命令行中添加了 --preserveConstEnums 指令,均会让当前 const 枚举转变为普通枚举
// 即会生成反向映射表;在实际开发中,一般情况你可能需要禁止掉该项,除非想要用于调试;
// 声明语 + 关键词 + 枚举名称
declear enum ChineseZodiac {
  rat = 1,
  cattle,
  tiger,
  rabbit,
  dragon
}
console.log(ChineseZodiac)
console.log(ChineseZodiac.rat)
// 你会发现,无论你是访问枚举本身还是内部成员,均会报错: ChineseZodiac is not defined
// 编译之后并没有生成该枚举,也就是说,声明的外部枚举是没有被编译的,导致在 runtime 的时候就会报错
// 这就让人很头疼,既然不能访问,那为何要能做出这个声明呢。

官网对其的描述是:外部枚举用来描述已经存在的枚举类型的形状,这样听起来很晦涩,下面是对其的释义:

  1. 外部枚举是为了描述当前环境中已经存在的对象的,这个对象可以存在任意的地方,但是一定是已声明的;
    1-1- 一个 .html 后缀文件,为了引入 .ts 文件编译之后的结果,用于调试
    index.html.png
    1-2- .ts 源文件
    index.ts.png
    1-3- 编译之后的 .js 文件
    index.js.png
    1-4- 其他 .js 资源文件
    other.js.png
    1-5- 访问结果
    declear-enum-result.png
    会发现是不会报错的,但是你可能会疑问了,这个不就是访问自己声明的一个变量吗,那跟 .ts 文件中声明的枚举有什么关系?图样图森破,外部枚举类似于 ts 的类型断言,只要在开发中有这个声明,意味着在当前开发环境上下文中一定存在当前这个对象,你可以随意使用当前对象;当然也就意味着你声明外部枚举的时候慎重,我是否真的需要这样做,不然 runtime 使用的时候就出错了;
  2. 外部枚举还可以防止声明枚举的命名冲突和成员冲突
    2-1- 我在上面文件结构基础上新增了一个 enum.ts 文件,并在里面声明了一个普通枚举,但是枚举成员和外部枚举成员相同
    enum.ts.png
    2-2- 之所以会有这样的提示,是 declear 的作用,因为 ts 类型系统能够侦测到当前整个文件目录上下文中的所有 declear 声明的变量,编译器也会有语法提示;
// 声明语 + 修饰符 + 关键词 + 枚举名称
declear const enum ChineseZodiac {
  rat = 1,
  cattle,
  tiger,
  rabbit,
  dragon
}
ChineseZodiac.dragon => 5/* dragon */
参考链接
  1. typescript中文网-枚举

2.关于不同枚举类型之间的区别

上一篇下一篇

猜你喜欢

热点阅读