04-掌握字面量类型与枚举,让你的类型再精确一些
课程
1
开篇:用正确的方式学习 TypeScript
已学完
学习时长: 7分24秒
2
工欲善其事:打造最舒适的 TypeScript 开发环境
学习时长: 19分14秒
3
进入类型的世界:理解原始类型与对象类型
学习时长: 37分40秒
4
掌握字面量类型与枚举,让你的类型再精确一些
学习时长: 24分53秒
5
函数与 Class 中的类型:详解函数重载与面向对象
学习时长: 50分40秒
6
探秘内置类型:any、unknown、never 与类型断言
学习时长: 34分58秒
7
类型编程好帮手:TypeScript 类型工具(上)
学习时长: 33分34秒
8
类型编程好帮手:TypeScript 类型工具(下)
学习时长: 40分52秒
9
类型编程基石:TypeScript 中无处不在的泛型
学习时长: 43分46秒
10
结构化类型系统:类型兼容性判断的幕后
学习时长: 20分48秒
11
类型系统层级:从 Top Type 到 Bottom Type
学习时长: 45分16秒
12
类型里的逻辑运算:条件类型与 infer
学习时长: 52分44秒
13
内置工具类型基础:别再妖魔化工具类型了!
学习时长: 44分47秒
14
反方向类型推导:用好上下文相关类型
学习时长: 18分37秒
15
函数类型:协变与逆变的比较
学习时长: 21分38秒
16
了解类型编程与类型体操的意义,找到平衡点
学习时长: 4分27秒
17
内置工具类型进阶:类型编程进阶
学习时长: 83分3秒
18
基础类型新成员:模板字符串类型入门
学习时长: 32分58秒
19
类型编程新范式:模板字符串工具类型进阶
学习时长: 69分27秒
20
工程层面的类型能力:类型声明、类型指令与命名空间
学习时长: 53分23秒
21
在 React 中愉快地使用 TypeScript:内置类型与泛型坑位
学习时长: 78分9秒
22
让 ESLint 来约束你的 TypeScript 代码:配置与规则集介绍
学习时长: 57分13秒
23
全链路 TypeScript 工具库,找到适合你的工具
学习时长: 25分13秒
24
说说 TypeScript 和 ECMAScript 之间那些事儿
学习时长: 29分14秒
25
装饰器与反射元数据:了解装饰器基本原理与应用
学习时长: 81分53秒
26
控制反转与依赖注入:基于装饰器的依赖注入实现
学习时长: 69分6秒
27
TSConfig 全解(上):构建相关配置
学习时长: 55分33秒
28
TSConfig 全解(下):检查相关、工程相关配置
学习时长: 76分19秒
29
基于 Prisma + NestJs 的 Node API :前置知识储备
学习时长: 44分50秒
30
基于 Prisma + NestJs 的 Node API :项目开发与基于 Heroku 部署
学习时长: 46分40秒
31
玩转 TypeScript AST:AST Checker 与 CodeMod
学习时长: 85分18秒
32
感谢相伴:是结束,也是开始
学习时长: 3分21秒
33
漫谈篇:面试中的 TypeScript
学习时长: 4分25秒
juejin_logo copyCreated with Sketch.

了解了原始类型与对象类型以后,我们已经能完成简单场景的类型标注了。但这还远远不够,我们还可以让这些类型标注更精确一些。比如,有一个接口结构,它描述了响应的消息结构:

interface IRes {
  code: number;
  status: string;
  data: any;
}

在大多数情况下,这里的 code 与 status 实际值会来自于一组确定值的集合,比如 code 可能是 10000 / 10001 / 50000,status 可能是 "success" / "failure"。而上面的类型只给出了一个宽泛的 number(string),此时我们既不能在访问 code 时获得精确的提示,也失去了 TypeScript 类型即文档的功能。

这个时候要怎么做?

本节代码见:Literal and Enum

字面量类型与联合类型

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

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

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

image.png

对于 declare var res: Res,你可以认为它其实就是快速生成一个符合指定类型,但没有实际值的变量,同时它也不存在于运行时中。上面引入了一些新的概念,我们来一个一个了解。

字面量类型

最开始你可能觉得很神奇,"success" 不是一个值吗?为什么它也可以作为类型?在 TypeScript 中,这叫做字面量类型(Literal Types),它代表着比原始类型更精确的类型,同时也是原始类型的子类型(关于类型层级,我们会在后面详细了解)。

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

const str: "linbudu" = "linbudu";
const num: 599 = 599;
const bool: true = true;

为什么说字面量类型比原始类型更精确?我们可以看这么个例子:

// 报错!不能将类型“"linbudu599"”分配给类型“"linbudu"”。
const str1: "linbudu" = "linbudu599";

const str2: string = "linbudu";
const str3: string = "linbudu599";

上面的代码,原始类型的值可以包括任意的同类型值,而字面量类型要求的是值级别的字面量一致

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

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

联合类型

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

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

这里有几点需要注意的:

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

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

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

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

type Code = 10000 | 10001 | 50000;

type Status = "success" | "failure";

除了原始类型的字面量类型以外,对象类型也有着对应的字面量类型。

对象字面量类型

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

interface Tmp {
  obj: {
    name: "linbudu",
    age: 18
  }
}

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

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

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

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

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

枚举

枚举并不是 JavaScript 中原生的概念,在其他语言中它都是老朋友了(Java、C#、Swift 等)。目前也已经存在给 JavaScript(ECMAScript)引入枚举支持的 proposal-enum 提案,但还未被提交给 TC39 ,仍处于 Stage 0 阶段。

如果要和 JavaScript 中现有的概念对比,我想最贴切的可能就是你曾经写过的 constants 文件了:

export default {
  Home_Page_Url: "url1",
  Setting_Page_Url: "url2",
  Share_Page_Url: "url3",
}

// 或是这样:
export const PageUrl = {
  Home_Page_Url: "url1",
  Setting_Page_Url: "url2",
  Share_Page_Url: "url3",
}

如果把这段代码替换为枚举,会是如下的形式:

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

const home = PageUrl.Home_Page_Url;

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

enum Items {
  Foo,
  Bar,
  Baz
}

在这个例子中,Items.Foo , Items.Bar , Items.Baz的值依次是 0,1,2 。

如果你只为某一个成员指定了枚举值,那么之前未赋值成员仍然会使用从 0 递增的方式,之后的成员则会开始从枚举值递增。

enum Items {
  // 0 
  Foo,
  Bar = 599,
  // 600
  Baz
}

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

const returnNum = () => 100 + 499;

enum Items {
  Foo = returnNum(),
  Bar = 599,
  Baz
}

但要注意,延迟求值的枚举值是有条件的。如果你使用了延迟求值,那么没有使用延迟求值的枚举成员必须放在使用常量枚举值声明的成员之后(如上例),或者放在第一位

enum Items {
  Baz,
  Foo = returnNum(),
  Bar = 599,
}

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

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

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

enum Items {
  Foo,
  Bar,
  Baz
}

const fooValue = Items.Foo; // 0
const fooKey = Items[0]; // "Foo"

要了解这一现象的本质,我们需要来看一看枚举的编译产物,如以上的枚举会被编译为以下 JavaScript 代码:

"use strict";
var Items;
(function (Items) {
    Items[Items["Foo"] = 0] = "Foo";
    Items[Items["Bar"] = 1] = "Bar";
    Items[Items["Baz"] = 2] = "Baz";
})(Items || (Items = {}));

obj[k] = v 的返回值即是 v,因此这里的 obj[obj[k] = v] = k 本质上就是进行了 obj[k] = vobj[v] = k 这样两次赋值。

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

enum Items {
  Foo,
  Bar = "BarValue",
  Baz = "BazValue"
}

// 编译结果,只会进行 键-值 的单向映射
"use strict";
var Items;
(function (Items) {
    Items[Items["Foo"] = 0] = "Foo";
    Items["Bar"] = "BarValue";
    Items["Baz"] = "BazValue";
})(Items || (Items = {}));

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

常量枚举

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

const enum Items {
  Foo,
  Bar,
  Baz
}

const fooValue = Items.Foo; // 0

它和普通枚举的差异主要在访问性与编译产物。对于常量枚举,你只能通过枚举成员访问枚举值(而不能通过值访问成员)。同时,在编译产物中并不会存在一个额外的辅助对象(如上面的 Items 对象),对枚举成员的访问会被直接内联替换为枚举的值。以上的代码会被编译为如下形式:

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

实际上,常量枚举的表现、编译产物还受到配置项 --isolatedModules 以及 --preserveConstEnums 等的影响,我们会在后面的 TSConfig 详解中了解更多。

总结与预告

在这一节中,我们了解了字面量类型和枚举的使用,包括字面量类型的分类,与联合类型的结合使用,以及枚举与其编译产物等等。

对于字面量类型,我们可以使用它来提供更精确的类型标注。比如,你可以将如用户类型与请求状态码这一类属性的类型,都使用字面量类型+联合类型的形式改写,获得更详细的类型信息与更严格的类型约束。

而对于枚举,我们可以使用它来替换掉之前使用对象进行常量收敛的代码,而如果你希望减少编译后的代码,可以进一步地使用在编译后会被完全抹除的常量枚举。

扩展阅读

类型控制流分析中的字面量类型

除了手动声明字面量类型以外,实际上 TypeScript 也会在某些情况下将变量类型推导为字面量类型,看这个例子:

image.png

image.png

你会发现,使用 const 声明的变量,其类型会从值推导出最精确的字面量类型。而对象类型则只会推导至符合其属性结构的接口,不会使用字面量类型:

image.png

要解答这个现象,需要你回想 let 和 const 声明的意义。我们知道,使用 let 声明的变量是可以再次赋值的,在 TypeScript 中要求赋值类型始终与原类型一致(如果声明了的话)。因此对于 let 声明,只需要推导至这个值从属的类型即可。而 const 声明的原始类型变量将不再可变,因此类型可以直接一步到位收窄到最精确的字面量类型,但对象类型变量仍可变(但同样会要求其属性值类型保持一致)。

这些现象的本质都是 TypeScript 的类型控制流分析,我们会在后面的类型系统部分中讲到。

留言
Ctrl + Enter
全部评论(75)
mylyh的头像
删除
“这些常量被真正地约束在一个命名空间” 这句话怎么理解呢
1
回复
Oct_20th的头像
删除
前端调包侠
请问我如果要定义一个比较精准的number类型需要怎么定义,比如我需要一个number的值在4-400的范围之间
点赞
5
删除
不能定义区间,你只能 4|5|6这样 好像
点赞
回复
删除
Enumerate类型是一个递归类型,它用来创建一个长度为 N 的数字数组。它的第一个参数 N 是数字类型,表示数组的长度。它的第二个参数 Acc 是数字数组类型,表示当前递归中数组的值。当 Acc 的长度等于 N 时,这个递归类型会停止递归,并返回 Acc 数组中的每一项。如果 Acc 的长度小于 N,那么递归类型会将当前的长度添加到 Acc 数组中,并继续递归。IntRange 是一个由两个数字类型参数 F 和 T 组成的类型,它表示从 F 到 T 的一个整数范围。它通过使用 Exclude 类型来排除由 Enumerate 类型生成的数组中的所有小于 F 的元素,从而实现F到T。
收起
image
5
回复
删除
真的强
Enumerate类型是一个递归类型,它用来创建一个长度为 N 的数字数组。它的第一个参数 N 是数字类型,表示数组的长度。它的第二个参数 Acc 是数字数组类型,表示当前递归中数组的值。当 Acc 的长度等于 N 时,这个递归类型会停止递归,并返回 Acc 数组中的每一项。如果 Acc 的长度小于 N,那么递归类型会将当前的长度添加到 Acc 数组中,并继续递归。IntRange 是一个由两个数字类型参数 F 和 T 组成的类型,它表示从 F 到 T 的一个整数范围。它通过使用 Exclude 类型来排除由 Enumerate 类型生成的数组中的所有小于 F 的元素,从而实现F到T。
点赞
回复
删除
虽然没看懂,但是还是想说:牛逼plus
Enumerate类型是一个递归类型,它用来创建一个长度为 N 的数字数组。它的第一个参数 N 是数字类型,表示数组的长度。它的第二个参数 Acc 是数字数组类型,表示当前递归中数组的值。当 Acc 的长度等于 N 时,这个递归类型会停止递归,并返回 Acc 数组中的每一项。如果 Acc 的长度小于 N,那么递归类型会将当前的长度添加到 Acc 数组中,并继续递归。IntRange 是一个由两个数字类型参数 F 和 T 组成的类型,它表示从 F 到 T 的一个整数范围。它通过使用 Exclude 类型来排除由 Enumerate 类型生成的数组中的所有小于 F 的元素,从而实现F到T。
点赞
回复
删除
数字大了会不会有性能问题
Enumerate类型是一个递归类型,它用来创建一个长度为 N 的数字数组。它的第一个参数 N 是数字类型,表示数组的长度。它的第二个参数 Acc 是数字数组类型,表示当前递归中数组的值。当 Acc 的长度等于 N 时,这个递归类型会停止递归,并返回 Acc 数组中的每一项。如果 Acc 的长度小于 N,那么递归类型会将当前的长度添加到 Acc 数组中,并继续递归。IntRange 是一个由两个数字类型参数 F 和 T 组成的类型,它表示从 F 到 T 的一个整数范围。它通过使用 Exclude 类型来排除由 Enumerate 类型生成的数组中的所有小于 F 的元素,从而实现F到T。
点赞
回复
嘎嘎🙊的头像
删除
打卡[力量]
点赞
回复
Zicxxciz的头像
删除
前端工程师 @ ****
打卡
点赞
回复
momomao的头像
删除
前端研发
打卡⁽⁽ૢ(⁎❝ົཽω❝ົཽ⁎)✧
点赞
回复
用户2362677620139的头像
删除
打卡
点赞
回复
雨飞飞雨的头像
删除
前端
每天早晨打卡
1
回复
樱桃小锤子的头像
删除
前端 @ 一只转行的咸鱼
作者大大,interface可以声明联合类型的对吧?我看外面有文章说和type的区别 其中一点就说type可以声明元组,联合类型,interface不可以[石化]
点赞
5
删除
(作者)
是的,接口只能作为联合类型子类型
点赞
回复
删除
好的
是的,接口只能作为联合类型子类型
点赞
回复
删除
interface可以声明联合类型,是什么意思,突然有点懵逼了,可以用代码举例一下吗,感谢
点赞
回复
删除
interface不可以声明联合类型
interface可以声明联合类型,是什么意思,突然有点懵逼了,可以用代码举例一下吗,感谢
点赞
回复
删除
interface {} 一般是个对象 type = 1 | 2 可以使用联合类型
interface可以声明联合类型,是什么意思,突然有点懵逼了,可以用代码举例一下吗,感谢
点赞
回复
寿司八哥的头像
删除
Web3D @ 图形起源
打卡
点赞
回复
疯子.也是猖狂的头像
删除
大佬们这个怎么打出来的
image
2
1
删除
(作者)
就是 === ,只不过字体看起来是连起来的,fira code
点赞
回复
btbrad的头像
删除
搬砖 @ 沪郊某工地
[赞]
点赞
回复
学会学会的头像
删除
前端小白
打卡
点赞
回复
涛涛_江的头像
删除
打卡
点赞
回复
用户1425291204412的头像
删除
这章前半部讲得很混乱
1
回复
siberianHusky的头像
删除
为什么字符串枚举成员只会进行单次映射?设置成双向是有什么问题么?
1
1
删除
我猜是因为容易重复 不安全
点赞
回复
徐妞妞的头像
删除
赞,不错,真好
点赞
回复
前端踩坑人员的头像
删除
打卡
点赞
回复
用户957782761656的头像
删除
图片全没了。。打不开,刷新了还是一样[流泪]
点赞
回复
扶我起来丶我还能学的头像
删除
const s = 'xxx' // 'xxx'
let s = 'xxx' // string
这两个的区别也是头一次才注意到,根据这两个的区别,想到了 `Object.freeze` 也是冻结数据禁止修改, 随即 试了下, 发现返回的果然是最精确的结果


const o1 = Object.freeze({ name: 'croatia', age: 18 })
// typeof o1 => Readonly<{ name: 'croatia', age: 18 }> ,这里到了最精确的结果


const originO = { name: 'croatia', age: 18 } // typeof originO => { name: string, age: number }

// 再将 originO 去冻结

const o2 = Object.freeze(originO) // 此时传递进去的类型就是稍微宽松点的类型了
// typeof o2 => Readonly<{ name: string, age: number }>
收起
点赞
1
删除
(作者)
这个其实是因为 Object.freeze 的返回值类型签名:freeze<T>(o: T): Readonly<T>,但 let 和 const 则更像是“内定”的特殊规则~
点赞
回复
qwertasdfg的头像
删除
一般什么情况下,需要定义延迟求值的枚举呢?是为了解决什么问题,可以举个具体的例子讲解下吗?
4
2
删除
同求
点赞
回复
删除
同求
点赞
回复
iarco的头像
删除
不度不度度
点赞
回复
萨博尔的头像
删除
前端开发
打卡
点赞
回复
juedui0769的头像
删除
打卡
点赞
回复
ayu的头像
删除
found a typo:isolateModules ->isolatedModules
点赞
回复
Juggernaut的头像
删除
求职
打卡
点赞
回复
Geek喜多川的头像
删除
面向工资💰编程 @ 人生不设限公司
打卡
点赞
回复
午与羽的头像
删除
Talking @ Imagination.
打卡[呲牙]
点赞
回复
衔泥的头像
删除
软件工程师
[赞]
1
回复
iTychooo君的头像
删除
移动开发工程师 @ 网信集团
打卡
点赞
回复
guaishou的头像
删除
点赞
回复
用户5457771076的头像
删除
打卡
点赞
回复
xuxuxujiajiajia的头像
删除
前端工程师 @ 某物联网公司
打卡
点赞
回复
Big_Rice的头像
删除
前端工程师 @ 未知
打卡
1
回复
遇你相识的头像
删除
前端开发工程师 @ 上海哔哩哔哩科技有限公司
请问一下,为何会报这个错,提示tmp is not defined。
image
点赞
2
删除
(作者)
因为 tmp 是声明的变量,不是真的存在的呀,这里只是为了验证类型层面的效果~
点赞
回复
删除
我也挺纠结,既然声明了变量为啥是not defined,declare是ts专用的吗,我看到有个编译好的文件。d。ts 专门是放declare的
因为 tmp 是声明的变量,不是真的存在的呀,这里只是为了验证类型层面的效果~
点赞
回复
遇你相识的头像
删除
前端开发工程师 @ 上海哔哩哔哩科技有限公司
打卡
点赞
回复
勇敢的小白兔的头像
删除
这个是不是写错了还是我看不懂这个
image
2
3
删除
(作者)
这个也还是合法的联合类型表达式,主要是为了格式化美观所以前面加了个|
3
回复
删除
好嘞,已学到[吃瓜群众],不知道大佬之后有没有出ts体操
这个也还是合法的联合类型表达式,主要是为了格式化美观所以前面加了个|
1
回复
删除
6
这个也还是合法的联合类型表达式,主要是为了格式化美观所以前面加了个|
点赞
回复
furfurJiang的头像
删除
前端 @ 程序江
没看懂常量枚举和普通枚举的区别,可以举个使用实例吗[难过]
点赞
2
删除
(作者)
其实基本也没有区别,只是常量枚举的编译产物比普通枚举少了一个额外的对象声明,如果你希望进一步减少编译代码就可以用常量枚举啦
6
回复
删除
好细节啊[抱拳]
其实基本也没有区别,只是常量枚举的编译产物比普通枚举少了一个额外的对象声明,如果你希望进一步减少编译代码就可以用常量枚举啦
点赞
回复
草原牧马人的头像
删除
前端工程师
图片没了啊
image
2
1
删除
(作者)
刷新下?这个图片地址是掘金图床,应该不会挂
1
回复
汪啊汪的头像
删除
挑个刺,这里多了个冒号[看]
image
1
1
删除
(作者)
已修正~
点赞
回复
user8280318367077的头像
删除
如果你使用了延迟求值,那么没有使用延迟求值的枚举成员必须放在使用常量枚举值声明的成员之后(如上例),或者放在第一位
是不是这个意思
image
点赞
1
删除
我理解就是看枚举成员的值是否可以自动推导出来,放在错误的位置上的话会导致值无法自动推导出来
点赞
回复
木子烁束岸的头像
删除
点赞
回复
学习要开心的头像
删除
饮水机旁
打卡
点赞
回复
爱吃鱼的桶哥Z的头像
删除
伪 · 全栈打杂攻城狮
打卡
点赞
回复
botmoon的头像
删除
端茶倒水负责人 @ 心脏抖动
打卡
点赞
回复
树洞的头像
删除
自动匿名机器人 @ 自动匿名机器人
如何分别获取枚举的所有值,或者所有key
点赞
2
删除
(作者)
枚举也是对象,因此使用 Obejct.keys/values 就行,类型层面的话,可以type K = keyof typeof UserType 拿到枚举Key的联合类型,但是值类型的话就不行了,type V = UserType[keyof UserType] 会拿到枚举所有的属性与方法的类型(包括内置方法的)
2
回复
删除
标记一下,keyof UserType拿到的是"toString" | "valueOf",所以type V是
image
枚举也是对象,因此使用 Obejct.keys/values 就行,类型层面的话,可以type K = keyof typeof UserType 拿到枚举Key的联合类型,但是值类型的话就不行了,type V = UserType[keyof UserType] 会拿到枚举所有的属性与方法的类型(包括内置方法的)
点赞
回复
codermao的头像
删除
web实习 @ 小红书
打卡打卡
点赞
回复
馍馍汉宝的头像
删除
打卡
点赞
回复
micstone的头像
删除
前端工程师
mark
点赞
回复
The action has been successful