编辑
2023-06-01
前端
00
请注意,本文编写于 538 天前,最后修改于 525 天前,其中某些信息可能已经过时。

目录

为什么需要字面量类型、联合类型、枚举类型
字面量类型
基础值字面量类型
对象字面量类型
联合类型
联合类型实现属性互斥
通过类型别名复用联合类型
枚举类型
枚举的递增
枚举和对象的区别
常量枚举

为什么需要字面量类型、联合类型、枚举类型

假设我们有一个接口结构,描述了响应的消息结构

ts
interface Res { code: number; status: string; data: any; }

大多数情况下code 与 status 实际值会来自于一组确定值的集合

比如:

  • code 可能是 0 / 200 / 10000
  • status 可能是 ‘success’ / "failure"

但是上面的类型标注太宽泛了,我们既不能在访问 code 时获得精确的提示,也失去了 TypeScript 类型即文档的功能。

这时就需要更精确的字面量类型、联合类型、枚举类型,提供精确地取值标注

例如,我们可以使用字面量类型、联合类型将上面的例子标注的更清楚

ts
interface Res { code: 10000 | 10001 | 50000; status: "success" | "failure"; data: any; }

字面量类型

直接使用值作为类型,即字面量类型(Literal Types),代表着比原始类型更精确的类型,同时也是原始类型的子类型

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

相关信息

无论是原始类型还是对象类型的字面量类型,它们的本质都是类型而不是值

它们在编译时同样会被擦除,同时也是被存储在内存中的类型空间而非值空间。

基础值字面量类型

ts
const str: "silon" = "silon"; const num: 599 = 599; const bool: true = true;

字面量类型比原始类型更精确:

  • 原始类型:值可以包括任意的同类型值
  • 字面量类型:要求值级别的字面量一致
ts
// 报错!不能将类型“"silon123"”分配给类型“"silon"”。 const str1: "silon" = "silon123"; const str2: string = "linbudu"; const str3: string = "silon123";

对象字面量类型

对象字面量类型就是一个对象类型的值,即对象的所有属性都使用字面量进行了定义

ts
interface Tmp { obj: { name: "linbudu", age: 18 } } const tmp: Tmp = { obj: { name: "linbudu", age: 18 } }

联合类型

联合类型代表了一组类型的可用集合

  • 只要最终赋值的类型属于联合类型的成员之一,就可以认为符合这个联合类型
  • 联合类型对其成员并没有任何限制,可以将各种类型混合到一起
ts
interface Tmp { mixed: true | string | 599 | {} | (() => {}) | (1 | 2) }

相关信息

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

联合类型实现属性互斥

ts
interface Tmp { user: | { vip: true; expires: string; } | { vip: false; promotion: string; }; } declare var tmp: Tmp; if (tmp.user.vip) { console.log(tmp.user.expires); }

在这里 user 有两种数据结构,expires 属性只会在一种情况下出现。 一般业务中,通过判断 vip 是否为 true,来收窄后面的类型判断

通过类型别名复用联合类型

ts
type Code = 10000 | 10001 | 50000; type Status = "success" | "failure";

枚举类型

枚举并不是 JavaScript 中原生的概念,目前已经加入 Es 提案,但仍处于 stage0 阶段

我们会在业务场景中,列举一系列的值

js
export const PageUrl = { Home_Page_Url: "url1", Setting_Page_Url: "url2", Share_Page_Url: "url3", }

使用枚举形式如下

ts
enum PageUrl { Home_Page_Url = "url1", Setting_Page_Url = "url2", Share_Page_Url = "url3", } const home = PageUrl.Home_Page_Url;

这么做的好处非常明显。

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

TypeScript 中也可以同时使用字符串枚举值和数字枚举值

enum Mixed { Num = 599, Str = "linbudu" }

枚举的递增

  • 如果你没有声明枚举的值,它会默认使用数字枚举,并且从 0 开始,以 1 递增:
ts
// Items.Foo , Items.Bar , Items.Baz的值依次是 0,1,2 。 enum Items { Foo, Bar, Baz }
  • 如果你只为某一个成员指定了枚举值,那么之前未赋值成员仍然会使用从 0 递增的方式,之后的成员则会开始从枚举值递增。
ts
enum Items { // 0 Foo, Bar = 599, // 600 Baz }
  • 在数字型枚举中,你可以使用延迟求值的枚举值。
    • 如果你使用了延迟求值,那么没有使用延迟求值的枚举成员必须放在使用常量枚举值声明的成员之后,或者放在第一位
ts
const returnNum = () => 100 + 499; // enum Items { Foo = returnNum(), Bar = 599, Baz } // enum Items { Baz, Foo = returnNum(), Bar = 599, }

枚举和对象的区别

枚举和对象的重要差异在于:

  • 对象:是单向映射的
    • 只能从键映射到键值。
  • 枚举:是双向映射的
    • 可以从枚举成员映射到枚举值
    • 可以从枚举值映射到枚举成员
ts
enum Items { Foo, Bar, Baz } const fooValue = Items.Foo; // 0 const fooKey = Items[0]; // "Foo"

相关信息

  • 仅有值为数字的枚举成员才能够进行这样的双向枚举
  • 字符串枚举成员仍然只会进行单次映射
ts
enum Items { Foo, Bar = "BarValue", Baz = "BazValue" } // 编译结果,只会进行 键-值 的单向映射 "use strict"; var Items; (function (Items) { // 数值类型 // 这里的 obj[obj[k] = v] = k 本质上就是进行了 obj[k] = v 与 obj[v] = k 这样两次赋值 Items[Items["Foo"] = 0] = "Foo"; // 非数字类型只有一次赋值 Items["Bar"] = "BarValue"; Items["Baz"] = "BazValue"; })(Items || (Items = {}));

常量枚举

常量枚举和枚举相似,只是其声明多了一个 const:

ts
const enum Items { Foo, Bar, Baz } const fooValue = Items.Foo; // 0

他与普通枚举的区别在于访问性与编译产物

  • 你只能通过枚举成员访问枚举值(而不能通过值访问成员)
  • 在编译产物中并不会存在一个额外的辅助对象(如上面的 Items 对象),对枚举成员的访问会被直接内联替换为枚举的值。

以上的代码会被编译为如下形式:

const fooValue = 0 /* Foo */; // 0

本文作者:Silon汐冷

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!