类型保护是一个执行运行时检查的表达式,以保证某个范围内的类型。类型保护的一个典型应用场景是缩小联合类型的类型范围。这是为了保证类型安全,即在运行时安全地访问特定类型对象中的特定属性或方法。
在这篇文章中,我将介绍 5 种实现类型保护的方法。
01、typeof 类型保护
首先我们来介绍一下比较常见的typeof类型防护。typeof 运算符可以在运行时获取对象的类型,该运算符返回以下可能的值:
- "string"
- "number"
- "bigint"
- "boolean"
- "symbol"
- "undefined"
- "object"
- "function"
因此使用 typeof 运算符,我们可以在运行时获取变量的实际类型。举个例子:
function printId(id: string | number) {
if (typeof id === "string") {
console.log(`ID: ${id.toUpperCase()}`);
} else if (typeof id === "number") {
console.log(`ID: ${id}`);
}
}
在上面的代码中,我们定义了一个 printId 函数,它包含一个 id 参数,其类型为字符串 | 数字联合类型。
在函数体中,我们使用 typeof 运算符来确定参数的实际类型。如果id参数是字符串类型,我们会将其值转换为大写后再输出。
那么为什么要使用 typeof 运算符来缩小 id 参数的类型呢?主要原因是为了保证运行时的类型安全。例如,当id参数的类型是数字类型时,但是我们调用id.toUpperCase()方法,就会抛出运行时异常。
在支持 TypeScript IntelliSense 的编辑器中,当您访问 id 参数的某些属性时,只能访问字符串和数字类型的公共属性。具体如下图所示:
02、instanceof 类型守卫
虽然typeof运算符可以区分不同的类型,但如果我们想判断一个对象是否是某个类的实例,从而安全地访问该实例上特有的属性或方法,那么typeof运算符就无能为力了。
对于这个需求,我们可以使用instanceof运算符。再次,我们举一个具体的例子:
class Shape {
constructor(public id: string) {}
}
class Circle extends Shape {
constructor(
public id: string,
public radius: number) {
super(id);
}
}
class Square extends Shape {
constructor(
public id: string,
public sideLength: number) {
super(id);
}
}
在上面的代码中,我们定义了一个Shape类,并基于它创建了两个子类。接下来,我们定义一个 printShapeInfo 函数来打印有关不同形状的信息:
function printShapeInfo(shape: Shape) {
if (shape instanceof Circle) {
console.log(`Circle's radius is: ${shape.radius}`);
} else if (shape instanceof Square) {
console.log(`Square's sideLength is: ${shape.sideLength}`);
}
}
在printShapeInfo函数体中,我们使用instanceof运算符来缩小形状参数的类型,从而输出不同形状的信息。在 if...else if 分支之外,我们只能访问 Circle 对象和 Square 对象共有的 id 属性。
03、in type guards
对于前面使用instanceof运算符实现类型保护的示例,我们还可以使用接口的形式来描述Shape、Circle和Square类型。
interface Shape {
id: string;
}
interface Circle extends Shape {
radius: number;
}
interface Square extends Shape {
sideLength: number;
}
由于TypeScript接口定义的类型在编译后并不会生成对应的类型,因此我们无法在运行时使用instanceof运算符进行类型检测。要实现printShapeInfo函数的功能,我们可以使用in运算符,具体实现如下:
function printShapeInfo(shape: Shape) {
if ("radius" in shape) {
console.log(`Circle's radius is: ${shape.radius}`);
} else if ("sideLength" in shape) {
console.log(`Square's sideLength is: ${shape.sideLength}`);
}
}
除了上述方法之外,我们还可以使用可判别联合类型来表示Shape类型:
type Circle = {
id: string;
type: "circle";
radius: number;
};
type Square = {
id: string;
type: "square";
sideLength: number;
};
type Shape = Circle | Square;
在Circle和Square类型中,type属性的类型是字符串文字类型,用于区分不同的形状,称为可区分属性。对于判别联合类型,结合switch…case语句,我们还可以实现printShapeInfo函数对应的功能。
function printShapeInfo(shape: Shape) {
switch (shape.type) {
case "circle":
console.log(`Circle's radius is: ${shape.radius}`);
break;
case "square":
console.log(`Square's sideLength is: ${shape.sideLength}`);
break;
default:
console.log("Unknown shape");
}
}
介绍完如何使用常见的 typeof、instanceof 和 in 运算符实现类型保护之后,我们来介绍一下如何定义用户自定义的类型保护。
04、用户定义的类型保护
为了演示用户定义的类型保护,让我们重新定义 3 种类型:
interface Shape {
id: string;
}
interface Circle extends Shape {
radius: number;
}
interface Square extends Shape {
sideLength: number;
}
定义完Shape相关的类型后,我们来定义用户自定义的类型保护函数:
function isCircle(shape: Shape): shape is Circle {
return "radius" in shape;
}
function isSquare(shape: Shape): shape is Square {
return "sideLength" in shape;
}
与普通函数相比,自定义类型保护函数返回类型谓词。上面代码中的 shape is Circle 就是所谓的类型谓词。谓词采用parameterName is Type 的形式,其中parameterName 必须是当前函数签名中的参数名称。
这样就可以理解isCircle用户自定义类型保护函数的作用了。如果函数返回值为true,则shape参数的类型为Circle类型。
现在我们有了 isCircle 和 isSquare 函数,我们可以在 printShapeInfo 函数中使用它们,如下所示:
function printShapeInfo(shape: Shape) {
if (isCircle(shape)) {
console.log(`Circle's radius is: ${shape.radius}`);
} else if (isSquare(shape)) {
console.log(`Square's sideLength is: ${shape.sideLength}`);
}
}
05、相等缩小类型防护
除了前面描述的 4 种类型保护方法之外,TypeScript 还支持使用 if/switch 语句和相等检查,例如 ===、!===、== 和 != 运算符来缩小变量的类型。
function printValues(a: string | number, b: string | string[]) {
if (a === b) {
console.log(a.toUpperCase()); // (parameter) a: string
console.log(b.toUpperCase()); // (parameter) b: string
} else {
console.log(a); // (parameter) a: string | number
console.log(b); // (parameter) b: string | string[]
}
}
上面的代码中,printValues函数支持a和b 2个参数,并且它们的类型都是联合类型。当a===b表达式的计算结果为true时,参数a和b的类型将缩小为字符串类型。当然,使用!==运算符也可以用来实现类型缩小。
function printValues2(a: string | number, b: string | string[]) {
if (a !== b) {
console.log(a); // (parameter) a: string | number
console.log(b); // (parameter) b: string | string[]
} else {
console.log(a.toLowerCase()); // (parameter) a: string
console.log(b.toLowerCase()); // (parameter) b: string
}
}
这就是关于 TypeScript 类型防护的全部内容。
总结
以上就是我今天想与你分享的5个TS的知识技能,希望对你有所帮助。