有一个接口结构,它描述了响应的消息结构:

  1. interface IRes {
  2. code: number;
  3. status: string;
  4. data: any;
  5. }

在大多数情况下,这里的 code 与 status 实际值会来自于一组确定值的集合,比如 code 可能是 10000 / 10001 / 50000,status 可能是 “success” / “failure”。

字面量类型与联合类型

我们可以使用联合类型加上字面量类型,把上面的例子改写成这样:

  1. interface Res {
  2. code: 10000 | 10001 | 50000;
  3. status: "success" | "failure";
  4. data: any;
  5. }

这个时候,我们就能在访问时获得精确地类型推导了。
image.png

字面量类型

在 TypeScript 中,像上面”success”这种叫做字面量类型(Literal Types),它代表着比原始类型更精确的类型,同时也是原始类型的子类型。
字面量类型主要包括字符串字面量类型数字字面量类型布尔字面量类型对象字面量类型,它们可以直接作为类型标注。

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

  1. interface Tmp {
  2. bool: true | false;
  3. num: 1 | 2 | 3;
  4. str: "lin" | "bu" | "du"
  5. }

联合类型

而联合类型你可以理解为,它代表了一组类型的可用集合,只要最终赋值的类型属于联合类型的成员之一,就可以认为符合这个联合类型。联合类型对其成员并没有任何限制,除了上面这样对同一类型字面量的联合,我们还可以将各种类型混合到一起:

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

这里有几点需要注意的:

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

联合类型的常用场景之一是通过多个对象类型的联合,来实现手动的互斥属性,即这一属性如果有字段1,那就没有字段2:

  1. interface Tmp {
  2. user:
  3. | {
  4. vip: true;
  5. expires: string;
  6. }
  7. | {
  8. vip: false;
  9. promotion: string;
  10. };
  11. }
  12. declare var tmp: Tmp;
  13. if (tmp.user.vip) {
  14. console.log(tmp.user.expires);
  15. }

在这个例子中,user 属性会满足普通用户与 VIP 用户两种类型,这里 vip 属性的类型基于布尔字面量类型声明。我们在实际使用时可以通过判断此属性为 true ,确保接下来的类型推导都会将其类型收窄到 VIP 用户的类型(即联合类型的第一个分支)。这一能力的使用涉及类型守卫与类型控制流分析。

对象字面量类型

类似的,对象字面量类型就是一个对象类型的值。当然,这也就意味着这个对象的值全都为字面量值:

  1. interface Tmp {
  2. obj: {
  3. name: "linbudu",
  4. age: 18
  5. }
  6. }
  7. const tmp: Tmp = {
  8. obj: {
  9. name: "linbudu",
  10. age: 18
  11. }
  12. }

对象字面量类型在实际开发中的使用较少,我们只需要了解。
无论是原始类型还是对象类型的字面量类型,它们的本质都是类型而不是值。它们在编译时同样会被擦除,同时也是被存储在内存中的类型空间而非值空间。

枚举

枚举的用法

在JavaScript 中,我们声明下面一个对象:

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

使用枚举就可以这样写:

  1. enum PageUrl {
  2. Home_Page_Url = "url1",
  3. Setting_Page_Url = "url2",
  4. Share_Page_Url = "url3",
  5. }
  6. const home = PageUrl.Home_Page_Url;

这么做的好处非常明显。首先,你拥有了更好的类型提示。其次,这些常量被真正地约束在一个命名空间下(上面的对象声明总是差点意思)。如果你没有声明枚举的值,它会默认使用数字枚举,并且从 0 开始,以 1 递增:

  1. enum Items {
  2. Foo,
  3. Bar,
  4. Baz
  5. }

在这个例子中,Items.Foo , Items.Bar , Items.Baz的值依次是 0,1,2 。
如果你只为某一个成员指定了枚举值,那么之前未赋值成员仍然会使用从 0 递增的方式,之后的成员则会开始从枚举值递增。

枚举是双向映射的,即你可以从枚举成员映射到枚举值,也可以从枚举值映射到枚举成员:

  1. enum Items {
  2. Foo,
  3. Bar,
  4. Baz
  5. }
  6. const fooValue = Items.Foo; // 0
  7. const fooKey = Items[0]; // "Foo"