<返回目录     Powered by claud/xia兄

第8课: 类型守卫

什么是类型守卫?

类型守卫是一种在运行时检查类型的表达式,用于在特定的作用域内缩小类型范围,让TypeScript能够更精确地推断类型。

typeof类型守卫

function padLeft(value: string, padding: string | number) {
  if (typeof padding === "number") {
    // 在这个代码块中,padding被缩小为number类型
    return Array(padding + 1).join(" ") + value;
  }
  if (typeof padding === "string") {
    // 在这个代码块中,padding被缩小为string类型
    return padding + value;
  }
  throw new Error(`Expected string or number, got '${padding}'.`);
}

// typeof只能识别以下类型
// "string", "number", "bigint", "boolean", "symbol", "undefined", "object", "function"

instanceof类型守卫

class Bird {
  fly() {
    console.log("Flying...");
  }
  layEggs() {
    console.log("Laying eggs...");
  }
}

class Fish {
  swim() {
    console.log("Swimming...");
  }
  layEggs() {
    console.log("Laying eggs...");
  }
}

function getRandomPet(): Bird | Fish {
  return Math.random() > 0.5 ? new Bird() : new Fish();
}

let pet = getRandomPet();

if (pet instanceof Bird) {
  pet.fly(); // 正确:pet被缩小为Bird类型
}
if (pet instanceof Fish) {
  pet.swim(); // 正确:pet被缩小为Fish类型
}

in操作符类型守卫

interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

function move(pet: Bird | Fish) {
  if ("fly" in pet) {
    // pet被缩小为Bird类型
    pet.fly();
  } else {
    // pet被缩小为Fish类型
    pet.swim();
  }
}

自定义类型守卫

使用类型谓词 parameterName is Type 创建自定义类型守卫:

interface Bird {
  fly(): void;
  layEggs(): void;
}

interface Fish {
  swim(): void;
  layEggs(): void;
}

// 自定义类型守卫函数
function isFish(pet: Bird | Fish): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Bird | Fish) {
  if (isFish(pet)) {
    pet.swim(); // pet被缩小为Fish类型
  } else {
    pet.fly(); // pet被缩小为Bird类型
  }
}

可辨识联合类型

使用共同的字面量类型属性来区分联合类型:

interface Square {
  kind: "square";
  size: number;
}

interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}

interface Circle {
  kind: "circle";
  radius: number;
}

type Shape = Square | Rectangle | Circle;

function area(s: Shape): number {
  switch (s.kind) {
    case "square":
      return s.size * s.size;
    case "rectangle":
      return s.width * s.height;
    case "circle":
      return Math.PI * s.radius ** 2;
  }
}

穷尽性检查

使用never类型确保所有情况都被处理:

function assertNever(x: never): never {
  throw new Error("Unexpected object: " + x);
}

function area(s: Shape): number {
  switch (s.kind) {
    case "square":
      return s.size * s.size;
    case "rectangle":
      return s.width * s.height;
    case "circle":
      return Math.PI * s.radius ** 2;
    default:
      return assertNever(s); // 如果有遗漏的case,这里会报错
  }
}

类型断言

// 尖括号语法
let someValue: any = "this is a string";
let strLength: number = (someValue).length;

// as语法(推荐,在JSX中只能使用这种)
let someValue2: any = "this is a string";
let strLength2: number = (someValue2 as string).length;

// 非空断言操作符
function liveDangerously(x?: number | null) {
  console.log(x!.toFixed()); // 断言x不是null或undefined
}

双重断言

// 有时需要先断言为any或unknown,再断言为目标类型
const a = (expr as any) as T;
const b = (expr as unknown) as T;
类型守卫的作用:
练习:
  1. 使用typeof创建一个函数,处理string和number类型的参数
  2. 使用instanceof判断对象是否为特定类的实例
  3. 创建一个自定义类型守卫函数
  4. 使用可辨识联合类型实现一个状态机