目前已出 重学 TS 专题(二十四篇),欢迎感兴趣的小伙关注 全栈修仙之路一块儿“重学TS”(扫描文末二维码)。
在 JavaScript 中布尔类型的变量含有有限范围的值,即 true
和 false
。而在 TypeScript 中使用枚举,你也能够自定义类似的类型。html
这是一个枚举的简单示例:git
enum NoYes { No, Yes, }
No
和 Yes
被称为枚举 NoYes
的成员。与对象字面量同样,尾随逗号是被容许的。对于 NoYes 枚举咱们可以轻易的访问它的成员,好比:github
function toChinese(value: NoYes) { switch (value) { case NoYes.No: return '否'; case NoYes.Yes: return '是'; } } assert.equal(toChinese(NoYes.No), '否'); assert.equal(toChinese(NoYes.Yes), '是');
每一个枚举成员都有一个 name 和一个 value。数字枚举成员值的默认类型是 number 类型。也就是说,每一个成员的值都是一个数字:正则表达式
enum NoYes { No, Yes, } assert.equal(NoYes.No, 0); assert.equal(NoYes.Yes, 1);
除了让 TypeScript 为咱们指定枚举成员的值以外,咱们还能够手动赋值:typescript
enum NoYes { No = 0, Yes = 1, }
这种经过等号的显式赋值称为 initializer
。若是枚举中某个成员的值使用显式方式赋值,但后续成员未显示赋值, TypeScript 会基于当前成员的值加 1 做为后续成员的值,好比如下 Enum 枚举中的成员 C:segmentfault
enum Enum { A, B, C = 4, D, E = 8, F, } assert.deepEqual( [Enum.A, Enum.B, Enum.C, Enum.D, Enum.E, Enum.F], [0, 1, 4, 5, 8, 9] );
常量的命名有几种约定:安全
Number.MAX_VALUE
;Symbol.asyncIterator
;NoYes
枚举。与 JavaScript 对象相似,咱们可使用方括号来引用包含非法字符的枚举成员:微信
enum HttpRequestField { 'Accept', 'Accept-Charset', 'Accept-Datetime', 'Accept-Encoding', 'Accept-Language', } assert.equal(HttpRequestField['Accept-Charset'], 1);
除了数字枚举,咱们还可使用字符串做为枚举成员值:session
enum NoYes { No = 'No', Yes = 'Yes', } assert.equal(NoYes.No, 'No'); assert.equal(NoYes.Yes, 'Yes');
对于纯字符串枚举,咱们不能省略任何初始化程序。dom
最后一种枚举称为异构枚举。异构枚举的成员值是数字和字符串的混合:
enum Enum { A, B, C = 'C', D = 'D', E = 8, F, } assert.deepEqual( [Enum.A, Enum.B, Enum.C, Enum.D, Enum.E, Enum.F], [0, 1, 'C', 'D', 8, 9] );
请注意,前面提到的规则也适用于此:若是先前的成员值为数字,则咱们能省略初始化程序。异构枚举因为其应用较少而不多使用。
目前 TypeScript 只支持将数字和字符串做为枚举成员值。不容许使用其余值,好比 symbols。
TypeScript 区分了三种指定枚举成员值的方式:
使用字面量进行初始化:
若是枚举只有字面量成员,咱们能够将这些成员用做类型(相似于数字字面量能够用做类型的方式):
enum NoYes { No = 'No', Yes = 'Yes', } function func(x: NoYes.No) { return x; } func(NoYes.No); // OK //@ts-ignore: Argument of type '"No"' is not assignable to // parameter of type 'NoYes.No'. func('No'); //@ts-ignore: Argument of type 'NoYes.Yes' is not assignable to // parameter of type 'NoYes.No'. func(NoYes.Yes);
此外,字面量枚举支持完整性检查(咱们将在后面进行介绍)。
TypeScript 2.6 支持在 .ts 文件中经过在报错一行上方使用// @ts-ignore
来忽略错误。
// @ts-ignore
注释会忽略下一行中产生的全部错误。建议实践中在@ts-ignore
以后添加相关提示,解释忽略了什么错误。请注意,这个注释仅会隐藏报错,而且咱们建议你少使用这一注释。
若是能够在编译时计算枚举成员的值,则该枚举成员是常量。所以,咱们能够隐式指定其值(即,让 TypeScript 为咱们指定它的值)。或者咱们能够显式指定它的值,而且仅容许使用如下语法:
+
,-
,~
+
,-
,*
,/
,%
,<<
,>>
,>>>
,&
,|
,^
如下是一个成员都是常量的枚举示例:
enum Perm { UserRead = 1 << 8, UserWrite = 1 << 7, UserExecute = 1 << 6, GroupRead = 1 << 5, GroupWrite = 1 << 4, GroupExecute = 1 << 3, AllRead = 1 << 2, AllWrite = 1 << 1, AllExecute = 1 << 0, }
若是枚举仅含有常量成员,则不能再将成员用做类型。可是咱们仍然能够进行完整性检查。
能够经过任意表达式设置枚举成员的值。例如:
enum NoYesNum { No = 123, Yes = Math.random(), // OK }
这是一个数字枚举。字符串枚举和异构枚举会有更多的限制。例如,咱们不能调用某些方法来设定枚举成员的值:
enum NoYesStr { No = 'No', //@ts-ignore: Computed values are not permitted in // an enum with string valued members. Yes = ['Y', 'e', 's'].join(''), }
在输出数字枚举的成员时,咱们只会看到数字:
enum NoYes { No, Yes } console.log(NoYes.No); console.log(NoYes.Yes); // Output: // 0 // 1
将枚举用做类型时,容许的值不仅是枚举成员的值 – 能够接受任何数字:
enum NoYes { No, Yes } function func(noYes: NoYes) {} func(33); // no error!
为何没有更严格的静态检查?Daniel Rosenwasser解释:
该行为是由按位运算引发的。有时SomeFlag.Foo | SomeFlag.Bar
打算产生另外一种SomeFlag
。相反,您最终获得了number
,而且你不想回退到SomeFlag
。我认为,若是咱们再次运行 TypeScript 以后仍然有枚举,那么咱们将为位标志创建一个单独的构造。
个人建议是使用字符串枚举:
enum NoYes { No='No', Yes='Yes' }
一方面,日志输出对人类更友好:
console.log(NoYes.No); console.log(NoYes.Yes); // Output: // 'No' // 'Yes'
另外一方面,咱们获得更严格的类型检查:
function func(noYes: NoYes) {} //@ts-ignore: Argument of type '"abc"' is not assignable // to parameter of type 'NoYes'. func('abc'); //@ts-ignore: Argument of type '"Yes"' is not assignable // to parameter of type 'NoYes'. func('Yes');
在 Node.js 文件系统模块中,几个函数具备参数模式。它的值用于经过 Unix 保留的编码来指定文件权限:
为三类用户指定了权限:
对于每一个类别,能够授予如下权限:
这意味着权限能够用 9 位表示(3 个类别,每一个类别具备 3 个权限):
用户 | 组 | 全部 | |
---|---|---|---|
权限 | r,w,x | r,w,x | r,w,x |
位 | 八、七、6 | 5 4 3 | 2 1 0 |
虽然在 Node.js 不是这样作,可是咱们可使用一个枚举来处理这些标志:
enum Perm { UserRead = 1 << 8, UserWrite = 1 << 7, UserExecute = 1 << 6, GroupRead = 1 << 5, GroupWrite = 1 << 4, GroupExecute = 1 << 3, AllRead = 1 << 2, AllWrite = 1 << 1, AllExecute = 1 << 0, }
位模式经过按位或(OR)组合:
// User can change, read and execute; everyone else can only read and execute assert.equal( Perm.UserRead | Perm.UserWrite | Perm.UserExecute | Perm.GroupRead | Perm.GroupExecute | Perm.AllRead | Perm.AllExecute, 0o755); // User can read and write; group members can read; everyone can’t access at all. assert.equal( Perm.UserRead | Perm.UserWrite | Perm.GroupRead, 0o640);
八进制,Octal,缩写 OCT 或 O,一种以 8 为基数的 计数法,采用 0,1,2,3,4,5,6,7 八个数字,逢八进 1。八进制 0o755 对应的十进制值是 493。
位模式背后的主要思想是存在一组标志,而且能够选择这些标志的任何子集。所以,使用 Set 选择子集是执行同一任务的一种更具描述性的方式:
enum Perm { UserRead, UserWrite, UserExecute, GroupRead, GroupWrite, GroupExecute, AllRead, AllWrite, AllExecute, } function writeFileSync( thePath: string, permissions: Set<Perm>, content: string) { // ··· } writeFileSync( '/tmp/hello.txt', new Set([Perm.UserRead, Perm.UserWrite, Perm.GroupRead]), 'Hello!');
有时,咱们有一组属于同类型的常量:
// Log level: const off = Symbol('off'); const info = Symbol('info'); const warn = Symbol('warn'); const error = Symbol('error');
这是一个很好的枚举用例:
enum LogLevel { off = 'off', info = 'info', warn = 'warn', error = 'error', }
该枚举的好处是:
LogLevel
内。LogLevel
只要须要这些常量之一,就可使用类型,而且 TypeScript 会执行静态检查。当使用布尔值表示替代方案时,枚举一般是一种更具自我描述性的选择。
例如,为了表示列表是否有序,咱们可使用布尔值:
class List1 { isOrdered: boolean; // ··· }
可是,枚举更具备自我描述性,并具备其余好处,即若是须要,咱们能够在之后添加更多选择项。
enum ListKind { ordered, unordered } class List2 { listKind: ListKind; // ··· }
一样,咱们能够经过布尔值或枚举来表示操做是成功仍是失败:
class Result1 { success: boolean; // ··· } enum ResultStatus { failure, success } class Result2 { status: ResultStatus; // ··· }
考虑如下建立正则表达式的函数。
const GLOBAL = 'g'; const NOT_GLOBAL = ''; type Globalness = typeof GLOBAL | typeof NOT_GLOBAL; function createRegExp(source: string, globalness: Globalness = NOT_GLOBAL) { return new RegExp(source, 'u' + globalness); } assert.deepEqual( createRegExp('abc', GLOBAL), /abc/ug);
若使用基于字符串的枚举更为方便:
enum Globalness { Global = 'g', notGlobal = '', } function createRegExp(source: string, globalness = Globalness.notGlobal) { return new RegExp(source, 'u' + globalness); } assert.deepEqual( createRegExp('abc', Globalness.Global), /abc/ug);
TypeScript 将枚举编译为 JavaScript 对象。例如,定义如下枚举:
enum NoYes { No, Yes, }
TypeScript 将该枚举编译为:
var NoYes; (function (NoYes) { NoYes[NoYes["No"] = 0] = "No"; NoYes[NoYes["Yes"] = 1] = "Yes"; })(NoYes || (NoYes = {}));
在此代码中,进行了如下赋值操做:
NoYes["No"] = 0; NoYes["Yes"] = 1; NoYes[0] = "No"; NoYes[1] = "Yes";
有两组赋值操做:
给定一个数字枚举:
enum NoYes { No, Yes, }
普通的映射是从成员名称到成员值:
// 静态查找 assert.equal(NoYes.Yes, 1); // 动态查找 assert.equal(NoYes['Yes'], 1);
数字枚举还支持从成员值到成员名称的反向映射:
assert.equal(NoYes[1], 'Yes');
基于字符串的枚举在运行时具备更简单的表示形式。
考虑如下枚举:
enum NoYes { No = 'NO!', Yes = 'YES!', }
它会被编译为如下 JavaScript 代码:
var NoYes; (function (NoYes) { NoYes["No"] = "NO!"; NoYes["Yes"] = "YES!"; })(NoYes || (NoYes = {}));
TypeScript 不支持基于字符串枚举的反向映射。
若是枚举以 const
关键字为前缀,则在运行时没有任何表示形式,而是直接使用成员的值。
首先咱们来看一下非 const 枚举:
enum NoYes { No, Yes, } function toChinese(value: NoYes) { switch (value) { case NoYes.No: return '否'; case NoYes.Yes: return '是'; } }
TypeScript 会将以上代码编译为:
var NoYes; (function (NoYes) { NoYes[NoYes["No"] = 0] = "No"; NoYes[NoYes["Yes"] = 1] = "Yes"; })(NoYes || (NoYes = {})); function toChinese(value) { switch (value) { case NoYes.No: return '否'; case NoYes.Yes: return '是'; } }
这与前面的代码基本一致,可是使用了 const 关键字:
const enum NoYes { No, Yes, } function toChinese(value: NoYes) { switch (value) { case NoYes.No: return '否'; case NoYes.Yes: return '是'; } }
如今,以前生成的 NoYes 对象消失了,仅保留了其成员的值:
function toChinese(value) { switch (value) { case 0 /* No */: return '否'; case 1 /* Yes */: return '是'; } }
TypeScript 将(非 const)枚举视为对象:
enum NoYes { No = 'No', Yes = 'Yes', } function func(obj: { No: string }) { return obj.No; } assert.equal( func(NoYes), 'No');
当咱们接受一个枚举成员值时,咱们一般要确保:
在如下代码中,咱们针对非法值采起了两种措施:
enum NoYes { No = 'No', Yes = 'Yes', } function toChinese(value: NoYes) { switch (value) { case NoYes.No: return '否'; case NoYes.Yes: return '是'; default: throw new TypeError('Unsupported value: ' + JSON.stringify(value)); } } assert.throws( //@ts-ignore: Argument of type '"Maybe"' is not assignable to // parameter of type 'NoYes'. () => toChinese('Maybe'), /^TypeError: Unsupported value: "Maybe"$/);
这些措施是:
NoYes
可防止将非法值传递给 value
参数;default
分支会抛出异常。咱们能够再采起一种措施。如下代码执行全面性检查:若是咱们忘记考虑全部枚举成员,TypeScript 将警告咱们。
enum NoYes { No = 'No', Yes = 'Yes', } function throwUnsupportedValue(value: never): never { throw new TypeError('Unsupported value: ' + value); } function toChinese2(value: NoYes) { switch (value) { case NoYes.No: return '否'; case NoYes.Yes: return '是'; default: throwUnsupportedValue(value); } }
全面性检查如何工做?对于每种状况,TypeScript 都会推断 value
的类型:
function toGerman2b(value: NoYes) { switch (value) { case NoYes.No: const x: NoYes.No = value; return '否'; case NoYes.Yes: const y: NoYes.Yes = value; return '是'; default: const z: never = value; throwUnsupportedValue(value); } }
在 default 分支中,TypeScript 会推断 value 的类型为 never
类型。可是,若是咱们添加一个成员 Maybe
到 NoYes
枚举中,以后 value
的推断类型是 NoYes.Maybe
,这时该变量的类型与 throwUnsupportedValue()
方法中参数的类型在静态上不兼容。所以,咱们在编译时会收到如下错误消息:
Argument of type 'NoYes.Maybe' is not assignable to parameter of type 'never'.
幸运的是,这种全面性检查也适用于如下 if
语句:
function toGerman3(value: NoYes) { if (value === NoYes.No) { return '否'; } else if (value === NoYes.Yes) { return '是'; } else { throwUnsupportedValue(value); } }
另外,若是咱们为如下 toChinese() 函数指定返回类型,也能够实现全面性检查:
enum NoYes { No = 'No', Yes = 'Yes', } function toChinese(value: NoYes): string { switch (value) { case NoYes.No: const x: NoYes.No = value; return '否'; case NoYes.Yes: const y: NoYes.Yes = value; return '是'; } }
若是咱们向 NoYes
中添加成员,则 TypeScript 会提醒 toChinese()
方法可能会返回 undefined
。
这种方法的缺点: 这种方法不适用于 if
语句。
咱们可使用 keyof
类型运算符建立类型,其元素是枚举成员的 key。当咱们这样作,咱们须要结合 keyof
和 typeof
一块儿使用:
enum HttpRequestKeyEnum { 'Accept', 'Accept-Charset', 'Accept-Datetime', 'Accept-Encoding', 'Accept-Language', } type HttpRequestKey = keyof typeof HttpRequestKeyEnum; // = 'Accept' | 'Accept-Charset' | 'Accept-Datetime' | // 'Accept-Encoding' | 'Accept-Language' function getRequestHeaderValue(request: Request, key: HttpRequestKey) { // ··· }
为何这样?这比直接定义 HttpRequestKey
类型更方便。
若是使用 keyof
不使用 typeof
,则会获得另外一个不太有用的类型:
type Keys = keyof HttpRequestKeyEnum; // = 'toString' | 'toFixed' | 'toExponential' | // 'toPrecision' | 'valueOf' | 'toLocaleString'
keyof HttpRequestKeyEnum
的结果与 keyof number
相同。
本文主要参考了“德国阮一峰” —— Axel Rauschmayer 大神的 numeric-enums 这篇文章,感兴趣的小伙伴可阅读原文哟。
https://2ality.com/2020/01/ty...
建立了一个 “重学TypeScript” 的微信群,想加群的小伙伴,加我微信 "semlinker",备注重学TS。本人的全栈修仙之路订阅号,会按期分享 Angular、TypeScript、Node.js/Java 、Spring 相关文章,欢迎感兴趣的小伙伴订阅哈!