关于枚举
反向映射原理
以前只知道如何建枚举,如何使用枚举,但是不知道如何通过Typescript拿到枚举内容,通过keyof typeof
反向映射获取枚举key
enum Enum {
A,
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
关于常量枚举
常量枚举的使用
- 大多数情况下,枚举是要给完全有效的解决方案,然而,又是要求严格,为了避免在访问枚举值需要生成额外的代码。可以使用const枚举,标记为常量。
const enum Enum{
A = 1,
B = A * 2,
}
常量枚举能使用常量枚举表达式,它与常规的枚举不同,他们编译期间会被完全删除。常量枚举成员在使用站点内联。
const enum Direction {
Up,
Down,
Left,
Right,
}
let directions = [
Direction.Up,
Direction.Down,
Direction.Left,
Direction.Right,
];
//以下是上面转化的结果,内联展示
"use strict";
let directions = [
0 /* Direction.Up */,
1 /* Direction.Down */,
2 /* Direction.Left */,
3 /* Direction.Right */,
];
常量枚举的陷阱
》 内联枚举值一开始很简单,但会带来一些影响。这些陷阱仅与环境const枚举(基本上是.d.ts文件中的const枚举)以及在项目之间共享有关。但是如果你正在发布或使用.d.ts文件。这些陷阱你就要考虑了。
- 由于isolatedModules文件中列出的原因改模式从根本上与环境常量枚举不兼容。这就意味着如果你发布环境常量枚举,饮用者将无法同时使用isolatedModules和那些枚举值。
isolatedModules(隔离模块),我们将Typescript代码生成Jacascript代码,会使用到Babel等其他转换器来执行,然而,其他转换器一次只能对单个文件进行操作,这就意味着他们不能应用依赖于丽洁完整类型操作系统的代码转换。此限制也适用于Typescript的ts.transileModule API,这些限制可能会导致一些Typescript的功能(如const枚举和命名空间)的运行时问题,设置隔离模块标志会告诉Typescript警告您,如果您编写的某些代码无法被单个文件转换过程正确解释。
上面的内容用白话文解释以下:
为什么需要设置isolatedModules为true
假设有如下两个 ts 文件,我们在 a.ts 中导出了 Test 接口,在 b.ts 中引入了 a.ts 中的 Test 接口,
然后又在 b.ts 将 Test 给导出。
// a.ts
export interface Test {}
// b.ts
import { Test } from './a';
export { Test };
这会造成一个什么问题呢,如 Babel 对 ts 转义时,它会先将 ts 的类型给删除,但是当碰到 b.ts 文件时,
Babel 并不能分析出 export { Test } 它到底导出的是一个类型还是一个实实在在的 js 方法或者变量,这时候 Babel 选择保留了 export。但是 a.ts 文件在转换时可以很容易的判定它就导出了一个类型,在转换为 js 时,a.ts 中的内容将被清空,而 b.ts 中导出的 Test 实际上是从 a.ts中引入的,这时候就会产生报错。
如何解决?
ts提供了import type or export type,用来明确标识我引入/导出的是一个类型,而不是一个变量或者方法,使用 import type 引入的类型 将在转换时js被删除掉
// b.ts
import { Test } from './a';
export type { Test };
- 你可以在编译时轻松的从以来的版本A中内联值,并在运行时导入版本B,版本A和B的枚举可以有不同的值,如果你时很小心,导致了意外的缺陷,就像做错了if语句的分支。这些错误特别有害,因为通常在构建项目的同时运行自动化测试,具有相同的依赖版本,完全忽略了这些错误
- importsNotUsedAsValues:"preserve"不会忽略用作值的const枚举的导入,但环境const枚举不保证运行时.js文件存在。无法解析的导入会在运行时导致错误。目前明确省略导入的常见方法,仅类型导入,不允许const枚举值
如何避免陷阱
- 根本不使用常量枚举,可以通过配置eslint[禁用const枚举](故障排除和常见问题解答 | typescript-eslint 中文网),显然,这避免了const枚举的任何问题,但会阻止你的项目内联自己的枚举。与其他项目的内联枚举不同,内联项目自己的枚举是没有问题的。并不会影响性能。
- 不要通过在preserveConstEnums的帮助下构建来发布环境常量枚举。这是Typescript项目本身内部采用的方法,preserveConstEnums为const枚举触发与普通枚举相同的Javascript,然后,你可以安全的从.d.ts文件在构建不种种删除const修饰符。
环境枚举
环境枚举用于描述已经存在的枚举类型的形状
declare enum Enum {
A = 1,
B,
C = 2,
}
环境枚举和非环境枚举之间的一个重要区别是,在常规枚举中,如果之前的枚举成员被认为是常量,那么没有初始化器的成员将被认为是常量。 相比之下,没有初始值设定项的环境(和非常量)枚举成员始终被视为已计算。
对象枚举
const enum EDirection {
Up,
Down,
Left,
Right,
}
const ODirection = {
Up: 0,
Down: 1,
Left: 2,
Right: 3,
} as const;
关于类型缩小
使用typeof
可以使用typeof运算符,提供我们关于我们在运行时拥有的值类型的基本信息
- "string"
- "number"
- "bigint"
- "boolean"
- "symbol"
- "undefined"
- "object"
- "function"
需要注意:由于Javascript中的一些怪癖,例如typeof不返回字符串null,typeof null 结果时object
&&、||、if、!
if判断通过以下是否返回true或者false,这里需要注意undefined和null
- 0
- NaN
- "" (空字符串)
- 0n (bigint 版本零)
- null
- undefined
switch、===、!==、==、!=
TypeScript 还使用 switch 语句和 ===、!==、== 和 != 等相等性检查来缩小类型。 例如:
function example(x: string | number, y: string | boolean) {
if (x === y) {
// We can now call any 'string' method on 'x' or 'y'.
x.toUpperCase();
(method) String.toUpperCase(): string
y.toLowerCase();
(method) String.toLowerCase(): string
} else {
console.log(x);
(parameter) x: string | number
console.log(y);
(parameter) y: string | boolean
}
}
使用in运算符
type Fish = { swim: () => void };
type Bird = { fly: () => void };
function move(animal: Fish | Bird) {
if ("swim" in animal) {
return animal.swim();
}
return animal.fly();
}
instanceof 缩小
JavaScript 有一个运算符用于检查一个值是否是另一个值的 “instance”。 更具体地说,在 JavaScript 中,x instanceof Foo 检查 x 的原型链是否包含 Foo.prototype。 虽然我们不会在这里深入探讨,并且当我们进入类时你会看到更多内容,但它们对于可以使用 new 构造的大多数值仍然很有用。 你可能已经猜到了,instanceof 也是一个类型保护,TypeScript 缩小了由 instanceof 保护的分支。
function logValue(x: Date | string) {
if (x instanceof Date) {
console.log(x.toUTCString());
(parameter) x: Date
} else {
console.log(x.toUpperCase());
(parameter) x: string
}
}
使用类型谓词is
要定义用户定义的类型保护,我们只需要定义一个返回类型为类型谓词的函数
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
//调用
// Both calls to 'swim' and 'fly' are now okay.
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
使用断言
使用断言函数缩小类型 as
判别联合
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}
//我们一般的写法都是
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
'shape.radius' is possibly 'undefined'.
}
}
上面是我一般的写法,这个时候就会遇到rect是非必填的字段,这个时候我们就会遇到报错,一般情况下我都是用下面的方法进行更改
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius! ** 2;
}
}
这次发现,上面的方法使用了非空断言(!)其实并不友好,避免减少断言的使用减少代码出错。我们可以从新规划下的我们的接口
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
type Shape = Circle | Square;
//使用
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
(parameter) shape: Circle
}
}
//注意如果为了代码清楚判断较多的情况可以直接使用switch
never类型
可以使用never类型来表示不应该存在的状态,never类型可以分配给每一个类型,但是,没有类型可分配给never(never本身除外)。这就意味着你可以使用缩小范围并依靠出现的never在switch语句中进行详尽检查
例如,将 default 添加到我们的 getArea 函数中,尝试将形状分配给 never,当处理完所有可能的情况时,不会引发错误。
type Shape = Circle | Square;
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}