基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

TypeScript中的类型守卫 (Type Guards)

知识点图片

在 TypeScript 中,类型守卫 (Type Guards) 是一种用于在运行时检查变量类型,从而在特定的代码块中收窄 (Narrowing) 变量类型的机制。

简单来说,TypeScript 的静态类型分析非常强大,但它无法总是知道运行时变量的确切类型(特别是对于联合类型 Union Types)。类型守卫就是你告诉编译器:“在这个 if 块里,你可以确信这个变量是 X 类型”。

以下是 TypeScript 中几种主要的类型守卫方式:


1. typeof 关键字

这是最基本的类型守卫,利用 JavaScript 原生的 typeof 运算符。适用于基本数据类型(string, number, boolean, symbol 等)。

typescript
function printId(id: number | string) {
  if (typeof id === "string") {
    // 在这个块中,TS 知道 id 是 string
    console.log(id.toUpperCase());
  } else {
    // 在这个块中,TS 知道 id 是 number
    console.log(id.toFixed(2));
  }
}

2. instanceof 关键字

用于检查一个值是否是某个类 (Class) 的实例。这对于处理类继承非常有用。

typescript
class Dog {
  bark() { console.log("Woof!"); }
}

class Cat {
  meow() { console.log("Meow!"); }
}

function interact(pet: Dog | Cat) {
  if (pet instanceof Dog) {
    // pet 被收窄为 Dog
    pet.bark();
  } else {
    // pet 被收窄为 Cat
    pet.meow();
  }
}

注意:instanceof 不能用于检查接口 (Interface),因为接口在编译后会被移除,运行时不存在。

3. in 操作符

用于检查对象中是否存在某个属性。这对于区分结构不同的对象(包括接口)非常有用。

typescript
interface Fish {
  swim: () => void;
}

interface Bird {
  fly: () => void;
}

function move(animal: Fish | Bird) {
  if ("swim" in animal) {
    // TS 推断 animal 必须是 Fish,因为它有 swim 属性
    animal.swim();
  } else {
    // TS 推断 animal 是 Bird
    animal.fly();
  }
}

4. 自定义类型守卫 (User-Defined Type Guards)

这是 TypeScript 最强大的特性之一。你可以编写一个函数,返回值的类型谓词为 parameter is Type

语法: arg is Type

typescript
interface Fish {
  swim: () => void;
}

interface Bird {
  fly: () => void;
}

// 自定义类型守卫函数
// 返回值类型不是 boolean,而是 `pet is Fish`
function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    // 在这里,pet 被确认为 Fish
    pet.swim();
  } else {
    // 在这里,TS 自动推断 pet 为 Bird
    pet.fly();
  }
}

为什么需要它? 当逻辑比较复杂,或者 typeof/in 不够用时,这种方式可以将类型判断逻辑封装起来复用。

5. 可辨识联合 (Discriminated Unions)

这是处理复杂数据结构最推荐的模式。通过在每个接口中添加一个共同的字面量类型属性(通常叫 kind, typetag),TypeScript 可以自动识别类型。

typescript
interface Circle {
  kind: "circle"; // 辨识特征
  radius: number;
}

interface Square {
  kind: "square"; // 辨识特征
  sideLength: number;
}

type Shape = Circle | Square;

function getArea(shape: Shape) {
  switch (shape.kind) {
    case "circle":
      // TS 知道这里 shape 是 Circle
      return Math.PI * shape.radius  2;
    case "square":
      // TS 知道这里 shape 是 Square
      return shape.sideLength  2;
  }
}

6. 断言函数 (Assertion Functions)

TypeScript 3.7 引入的特性。如果函数抛出错误,则表示参数不符合类型;如果函数正常返回,则参数符合类型。

语法: asserts conditionasserts arg is Type

typescript
function assertIsString(val: any): asserts val is string {
  if (typeof val !== "string") {
    throw new Error("Not a string!");
  }
}

function processValue(val: any) {
  assertIsString(val);
  //在此行之后,TS 知道 val 是 string
  console.log(val.toUpperCase());
}

总结:什么时候用哪个?

  1. 基本类型 (string, number):使用 typeof
  2. 类实例:使用 instanceof
  3. 简单对象属性差异:使用 in
  4. 复杂的接口判断:使用 自定义类型守卫 (is)
  5. 处理多种状态或对象类型:使用 可辨识联合 (kind 属性)(这是最优雅的方式)。
  6. 验证输入/抛出错误:使用 断言函数 (asserts)

类型守卫的核心价值在于:它让你在写代码时既能享受动态语言的灵活性(联合类型),又能保持静态语言的类型安全(类型收窄)。

00:00
00:00