文章目录

TypeScript 类型守卫:类型谓词与类型缩小

发布于 2026-04-18 14:22:29 · 浏览 12 次 · 评论 0 条

TypeScript 类型守卫:类型谓词与类型缩小

TypeScript 的联合类型允许变量接受多种类型,但也带来了访问属性时的类型不确定性。类型守卫是一套运行时检查机制,用于在条件块中“缩小”类型的范围,确保代码安全。本文将介绍如何利用 typeofininstanceof 以及自定义的“类型谓词”来精准控制类型流。


1. 理解类型缩小机制

当 TypeScript 推断出一个变量可能是 stringnumber 时,直接调用 string 特有的方法(如 .toUpperCase())会报错。通过类型守卫,我们可以在特定代码块内告诉编译器:“在这个分支里,变量一定是 string”。

下面是类型守卫的工作原理流程:

graph LR A["输入: 联合类型"] --> B{执行类型检查} B -- "条件成立" --> C["范围缩小: 具体类型"] B -- "条件不成立" --> D["范围缩小: 排除该类型"] C --> E["安全访问: 具体类型的方法/属性"]

2. 使用基础类型检查

TypeScript 原生支持一些检查表达式,能自动触发类型缩小。

2.1 使用 typeof 检查原始类型

处理 原始类型(如 stringnumberboolean)的联合类型时,使用 typeof 运算符是最直接的方式。

编写 以下代码定义一个处理 stringnumber 的函数:

function printId(id: string | number) {
  // 在这里,id 可能是 string 也可能是 number
  if (typeof id === "string") {
    // 此处 id 的类型被缩小为 string
    console.log(id.toUpperCase());
  } else {
    // 此处 id 的类型被缩小为 number
    console.log(id.toFixed(2));
  }
}

注意 typeof 的返回值字符串必须与 TypeScript 类型一一对应。检查 "object" 时要小心,因为 null 和数组在 JavaScript 中也被视为 object

2.2 使用 instanceof 检查类实例

处理 类的实例时,使用 instanceof 运算符。这会检查构造函数的 prototype 属性是否出现在对象的原型链中。

定义 两个类并编写处理函数:

class ErrorLog {
  constructor(public message: string) {}
}

class WarnLog {
  constructor(public code: number) {}
}

function handleLog(log: ErrorLog | WarnLog) {
  if (log instanceof ErrorLog) {
    // TypeScript 知道这里 log 是 ErrorLog
    console.log(log.message);
  } else {
    // TypeScript 知道这里 log 是 WarnLog
    console.log(log.code);
  }
}

2.3 使用 in 操作符检查属性存在性

处理 没有共同父类的对象结构时,使用 in 操作符检查特定属性是否存在。

编写 逻辑来判断对象是“鸟”还是“鱼”:

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

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

function moveInWater(animal: Bird | Fish) {
  if ("swim" in animal) {
    // 缩小为 Fish
    animal.swim();
  } else {
    // 缩小为 Bird
    animal.fly();
  }
}

3. 掌握类型谓词

当逻辑复杂(例如需要检查多个字段或进行数值计算)无法通过简单的 typeofin 表达时,我们需要自定义类型守卫函数。

3.1 定义类型谓词

类型谓词的语法格式为 parameterName is Type,它作为函数的返回值类型声明。

创建 一个判断变量是否为 Fish 的函数:

function isFish(pet: Bird | Fish): pet is Fish {
  // 实现逻辑:检查是否有 swim 方法
  return (pet as Fish).swim !== undefined;
}

这里 pet is Fish 就是类型谓词。它告诉 TypeScript:如果 isFish 返回 true,那么传入的 pet 参数就是 Fish 类型。

3.2 应用类型谓词

调用 自定义守卫函数来控制代码逻辑:

function getPetFood(pet: Bird | Fish): string {
  if (isFish(pet)) {
    // 此处 pet 被识别为 Fish
    return "Fish Food";
  } else {
    // 此处 pet 被识别为 Bird
    return "Bird Seed";
  }
}

如果不用类型谓词,而直接写 (pet as Fish).swim,TypeScript 会认为这是类型断言,在 else 分支中依然无法确定 petBird使用 谓词能让编译器智能地在两个分支里正确切换类型。


4. 利用可辨识联合

这是一种结构性模式,要求联合类型中的每个对象都包含一个字面量类型的公共属性(通常是 kindtype),用于区分不同的成员。

定义 包含 kind 属性的接口:

interface Circle {
  kind: "circle"; // 可辨识属性
  radius: number;
}

interface Square {
  kind: "square"; // 可辨识属性
  sideLength: number;
}

type Shape = Circle | Square;

检查 kind 属性来缩小类型:

function getArea(shape: Shape): number {
  if (shape.kind === "circle") {
    // TypeScript 推断出 shape 是 Circle
    return Math.PI * shape.radius * shape.radius;
  } else {
    // TypeScript 推断出 shape 是 Square
    return shape.sideLength * shape.sideLength;
  }
}

这种方式比 in 操作符更严格,因为它检查的是具体的字面量值,极大地减少了出错的可能性。


5. 类型守卫策略对比

下表总结了不同场景下应选择的守卫策略。

守卫类型 适用场景 语法示例 特点
typeof 原始类型 (string, number) typeof x === "string" 简单直接,仅限基本类型
instanceof 类实例 (new Class()) x instanceof MyClass 依赖原型链,不适用于接口
in 对象结构 ({a: 1}, {b: 2}) "prop" in x 检查属性名,通用性强
类型谓词 复杂逻辑判断 x is Type 可复用,逻辑封装在函数中
可辨识联合 带有标签的联合对象 shape.kind === "circle" 类型最安全,推荐模式

6. 断言函数

除了“检查”类型,我们有时需要“断言”类型,如果断言失败则抛出错误。这在处理从 API 获取的不确定数据时非常有用。

定义 一个断言函数,返回类型使用 asserts condition

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

调用 该函数处理输入:

function processInput(input: unknown) {
  assertIsString(input);

  // 执行过后,input 在此作用域内被视为 string
  console.log(input.toUpperCase());
}

如果 input 不是字符串,程序会抛出异常停止执行;如果是字符串,代码继续运行且类型安全得到保证。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文