字面量类型与枚举


# 字面量类型与枚举

字面量类型以及枚举,在某些方面其实可以理解为是原始类型与对象类型的进一步延伸,也同样是日常会被重度使用的语法。

# 字面量类型

字面量类型主要包括字符串字面量类型数字字面量类型布尔字面量类型对象字面量类型,它们可以直接作为类型标注:

const str: "apple" = "apple";
const num: 599 = 599;
const bool: true = true;
1
2
3

没错,所谓的字面量类型,就是把一个「具体的值」作为一种类型。它代表着比原始类型更精确的类型,同时也是原始类型的子类型:

  • 原始类型的值可以包括任意的同类型值
  • 字面量类型要求的是值级别的字面量一致

# 对象字面量类型

原始类型的字面量类型比较简单,这里单独讲一下对象字面量类型。

对象字面量类型意味着这个对象的值全都为字面量值:

interface Tmp {
  obj: {
    name: "张三",
    age: 13
  }
}

const tmp: Tmp = {
  obj: {
    name: "张三",
    age: 13
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

如果要实现一个对象字面量类型,意味着完全的实现这个类型每一个属性的每一个值。对象字面量类型在实际开发中的使用较少,只需要了解一下。

# 字面量类型的使用

单独使用字面量类型比较少见,因为单个字面量类型并没有什么实际意义。它通常和联合类型(|)一起使用,表达一组字面量类型:

interface Tmp {
  bool: true | false;
  num: 1 | 2 | 3;
  str: "apple" | "pear" | "banana"
}
1
2
3
4
5

# 联合类型

联合类型代表了一组类型的可用集合,只要最终赋值的类型属于联合类型的成员之一,就可以认为符合这个联合类型。

联合类型对其成员并没有任何限制,可以将各种类型混合到一起:

interface Tmp {
  mixed: true | string | 599 | {} | (() => {}) | (1 | 2)
}
1
2
3

这里有几点需要注意的:

  • 对于联合类型中的函数类型,需要使用括号 () 包裹起来。
  • 函数类型并不存在字面量类型,因此这里的 (() => {}) 就是一个合法的函数类型。
  • 可以在联合类型中进一步嵌套联合类型,但这些嵌套的联合类型最终都会被展平到第一级中。

可以通过类型别名来复用一组字面量联合类型:

type Code = 10000 | 10001 | 50000;

type Status = "success" | "failure";
1
2
3

总的来说,在需要更精确类型的情况下,我们可以使用字面量类型加上联合类型的方式,将类型从 string 这种宽泛的原始类型直接收窄到 "resolved" | "pending" | "rejected" 这种精确的字面量类型集合。

需要注意的是,无论是原始类型还是对象类型的字面量类型,它们的本质都是类型而不是值。它们在编译时同样会被擦除,同时也是被存储在内存中的类型空间而非值空间

# 枚举

如果说字面量类型是对原始类型的进一步扩展(对象字面量类型的使用较少),那么枚举在某些方面则可以理解为是对对象类型的扩展。

枚举的概念有点类似于 JavaScript 中的常量文件:

export const PageUrl = {
  Home_Page_Url: "url1",
  Setting_Page_Url: "url2",
  Share_Page_Url: "url3",
}
1
2
3
4
5

替换为枚举,会是如下的形式:

enum PageUrl {
  Home_Page_Url = "url1",
  Setting_Page_Url = "url2",
  Share_Page_Url = "url3",
}

const home = PageUrl.Home_Page_Url;
1
2
3
4
5
6
7

这么做的好处是:

  • 拥有了更好的类型提示。
  • 这些常量被真正地约束在一个命名空间下(上面的对象声明总是差点意思)。

# 枚举值的递增

  • 如果你没有声明枚举的值,它会默认使用数字枚举,并且从 0 开始,以 1 递增。
  • 而如果你只为某一个成员指定了枚举值,那么之前未赋值成员仍然会使用从 0 递增的方式,之后的成员则会开始从枚举值递增。
enum Items {
  // 0 
  Foo,
  Bar = 599,
  // 600
  Baz
}
1
2
3
4
5
6
7

# 数字枚举

在数字型枚举中,可以使用延迟求值的枚举值,比如函数:

const returnNum = () => 100 + 499;

enum Items {
  Foo = returnNum(),
  Bar = 599,
  Baz
}
1
2
3
4
5
6
7

但要注意,如果使用了延迟求值,那么没有使用延迟求值的枚举成员必须放在使用常量枚举值声明的成员之后,或者放在第一位:

# 双向映射特性

枚举和对象的重要差异在于,对象是单向映射的,我们只能从键映射到键值。而枚举是双向映射的,即可以从枚举成员映射到枚举值,也可以从枚举值映射到枚举成员:

enum Items {
  Foo,
  Bar,
  Baz
}

const fooValue = Items.Foo; // 0
const fooKey = Items[0];    // "Foo"
1
2
3
4
5
6
7
8

但需要注意的是,仅有值为数字的枚举成员才能够进行这样的双向枚举,字符串枚举成员仍然只会进行单次映射。

# 常量枚举

除了数字枚举与字符串枚举这种分类以外,其实还存在着普通枚举与常量枚举这种分类方式。

const enum Items {
  Foo,
  Bar,
  Baz
}

const fooValue = Items.Foo; // 0
1
2
3
4
5
6
7

它和普通枚举的差异在于:对于常量枚举,只能通过枚举成员访问枚举值(而不能通过值访问成员)。

(完)