第二篇 TypeScript 【 typeScript 断言 + typeScript 类型守卫 + typeScript 联合类型、可辨识联合和类型别名 + typeScript 交叉类型】

发布时间 2023-03-28 15:53:23作者: caix-1987

typeScript 断言

1、有时候你会遇到这样的情况,你会比 TypeScript 更了解某个值的详细信息,通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型

2、通过 类型断言 这种方式可以告诉编译器,“相信我,我知道自己在干什么”

3、类型断言 好比其他语言里的类型转换,但是不进行特殊的数据检查和解构

4、类型断言 没有运行时的影响,只是在编译阶段起作用

5、类型断言有两种形式

   1、“尖括号” 语法
   
   2、as 语法
“尖括号” 语法
let someValue: any = "this is a string";

let strLength: number = (<string>someValue).length;
as 语法
let someValue: any = "this is a string";

let strLength: number = (someValue as string).length;

typeScript 类型守卫 【 类型保护 】

1、类型保护 是可执行运行时检查的一种表达式,用于确保该类型在一定的范围内

2、类型保护 可以保证一个字符串是一个字符串,尽管它的值也可以是一个数值

3、类型保护 与特性检测并不是完全不同,其主要思想是尝试检测属性、方法或原型,以确定如何处理值

4、目前主要有 四种 方式来实现 类型保护

   1、in 关键字
   
   2、typeof 关键字
   
   3、instanceof 关键字
   
   4、自定义类型保护的类型谓词
in 关键字
interface Admin {
  name: string;
  privileges: string[];
}

interface Employee {
  name: string;
  startDate: Date;
}

type UnknownEmployee = Employee | Admin;

function printEmployeeInformation(emp: UnknownEmployee) {
  console.log("Name: " + emp.name);
  if ("privileges" in emp) {
    console.log("Privileges: " + emp.privileges);
  }
  if ("startDate" in emp) {
    console.log("Start Date: " + emp.startDate);
  }
}
typeof 关键字
function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
      return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
      return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

1、typeof 类型保护只支持两种形式:typeof v === "typename" 和 typeof v !== typename

2、"typename" 必须是 "number", "string", "boolean" 或 "symbol"

3、TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护
instanceof 关键字
interface Padder {
  getPaddingString(): string;
}

class SpaceRepeatingPadder implements Padder {
  constructor(private numSpaces: number) {}
  getPaddingString() {
    return Array(this.numSpaces + 1).join(" ");
  }
}

class StringPadder implements Padder {
  constructor(private value: string) {}
  getPaddingString() {
    return this.value;
  }
}

let padder: Padder = new SpaceRepeatingPadder(6);

if (padder instanceof SpaceRepeatingPadder) {
  // padder的类型收窄为 'SpaceRepeatingPadder'
}
补充 TypeScript 中 implements 与 extends 的区别
1、implements

   实现,一个新的类,从父类或者接口实现所有的属性和方法,同时可以重写属性和方法,包含一些新的功能

2、extends

   继承,一个新的接口或者类,从父类或者接口继承所有的属性和方法,不可以重写属性,但可以重写方法
   

interface IPerson {
  age: number;
  name: string;
}

interface IPeoPle extends IPerson {
  sex: string;
}

class User implements IPerson {
  age: number;
  name: string;
}
interface IRoles extends User{

}
class Roles extends User{
  
}

注意

   1、接口不能实现接口或者类,所以实现只能用于类身上,即类可以实现接口或类
   
   2、接口可以继承接口或类
   
   3、类不可以继承接口,类只能继承类
   
   4、可多继承或者多实现
自定义类型保护的类型谓词
function isNumber(x: any): x is number {
  return typeof x === "number";
}

function isString(x: any): x is string {
  return typeof x === "string";
}

typeScript 联合类型和类型别名

联合类型
1、联合类型通常与 null 或 undefined 一起使用

const sayHello = (name: string | undefined) => {
  /* ... */
};

这里 name 的类型是 string | undefined 意味着可以将 string 或 undefined 的值传递给sayHello 函数

sayHello("Semlinker");
sayHello(undefined);

2、通过这个示例,可以凭直觉知道类型 A 和类型 B 联合后的类型是同时接受 A 和 B 值的类型
可辨识联合
1、TypeScript 可辨识联合【 Discriminated Unions 】类型,也称为代数数据类型或标签联合类型

2、TypeScript 可辨识联合 包含 3 个要点

   1、可辨识
   
   2、联合类型
   
   3、类型守卫
可辨识
可辨识要求联合类型中的每个元素都含有一个单例类型属性

enum CarTransmission {
  Automatic = 200,
  Manual = 300
}

interface Motorcycle {
  vType: "motorcycle"; // discriminant
  make: number; // year
}

interface Car {
  vType: "car"; // discriminant
  transmission: CarTransmission
}

interface Truck {
  vType: "truck"; // discriminant
  capacity: number; // in tons
}

在上述代码中,我们分别定义了 Motorcycle、 Car 和 Truck 三个接口

在这些接口中都包含一个 vType 属性,该属性被称为可辨识的属性,而其它的属性只跟特性的接口相关
联合类型
基于前面定义了三个接口,我们可以创建一个 Vehicle 联合类型

type Vehicle = Motorcycle | Car | Truck;

现在我们就可以开始使用 Vehicle 联合类型,对于 Vehicle 类型的变量,它可以表示不同类型的车辆
类型守卫
下面我们来定义一个 evaluatePrice 方法,该方法用于根据车辆的类型、容量和评估因子来计算价格,具体实现如下

const EVALUATION_FACTOR = Math.PI; 
function evaluatePrice(vehicle: Vehicle) {
  return vehicle.capacity * EVALUATION_FACTOR;
}

const myTruck: Truck = { vType: "truck", capacity: 9.5 };
evaluatePrice(myTruck);

对于以上代码,TypeScript 编译器将会提示以下错误信息

Property 'capacity' does not exist on type 'Vehicle'.
Property 'capacity' does not exist on type 'Motorcycle'

原因是在 Motorcycle 接口中,并不存在 capacity 属性,而对于 Car 接口来说,它也不存在 capacity 属性

这时,我们可以使用类型守卫。下面我们来重构一下前面定义的 evaluatePrice 方法,重构后的代码如下

function evaluatePrice(vehicle: Vehicle) {
  switch(vehicle.vType) {
    case "car":
      return vehicle.transmission * EVALUATION_FACTOR;
    case "truck":
      return vehicle.capacity * EVALUATION_FACTOR;
    case "motorcycle":
      return vehicle.make * EVALUATION_FACTOR;
  }
}

我们使用 switch 和 case 运算符来实现类型守卫,从而确保在 evaluatePrice 方法中,我们可以安全地访问 vehicle 对象中的所包含的属性,来正确的计算该车辆类型所对应的价格
类型别名
类型别名用来给一个类型起个新名字

type Message = string | string[];

let greet = (message: Message) => {
  // ...
};

typeScript 交叉类型 【 type C = A & B 】

1、TypeScript 交叉类型是将多个类型合并为一个类型

2、这可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性

interface IPerson {
  id: string;
  age: number;
}

interface IWorker {
  companyId: string;
}

type IStaff = IPerson & IWorker;

const staff: IStaff = {
  id: 'E1006',
  age: 33,
  companyId: 'EFT'
};

console.dir(staff)

在上面示例中,首先为 IPerson 和 IWorker 类型定义了不同的成员,然后通过 & 运算符定义了 IStaff 交叉类型,所以该类型同时拥有 IPerson 和 IWorker 这两种类型的成员