五种在 TypeScript 中使用类型保护的方法

类型保护是一个执行运行时检查的表达式,以保证某个范围内的类型。类型保护的一个典型应用场景是缩小联合类型的类型范围。这是为了保证类型安全,即在运行时安全地访问特定类型对象中的特定属性或方法。

五种在 TypeScript 中使用类型保护的方法

在这篇文章中,我将介绍 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的知识技能,希望对你有所帮助。

文章版权声明

 1 原创文章作者:烧星盟友,如若转载,请注明出处: https://www.52hwl.com/31830.html

 2 温馨提示:软件侵权请联系469472785#qq.com(三天内删除相关链接)资源失效请留言反馈

 3 下载提示:如遇蓝奏云无法访问,请修改lanzous(把s修改成x)

 免责声明:本站为个人博客,所有软件信息均来自网络 修改版软件,加群广告提示为修改者自留,非本站信息,注意鉴别

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023年7月14日 上午12:00
下一篇 2023年7月15日