TypeScript 类型断言与守卫

处理联合类型和动态数据时,TS 有时无法精确知道当前值的类型,需要你"帮忙"告诉它。帮忙的方式分两类:类型断言(as,你拍板说"就是它")和类型守卫(用判断逻辑让 TS 自动收窄)。这一章讲清楚它们的用法和区别。

1. 类型断言:as

类型断言是你单方面告诉编译器"我确信这个值是这个类型"。最常见的场景是从 DOM 拿元素——TS 只知道它是宽泛的 HTMLElement,但你知道它是个 input

// 类型断言:告诉编译器"我确信它是这个类型"
// 语法:值 as 类型,或 <类型>值(后者不推荐,和 JSX 冲突)

// 场景:从 DOM 拿到的元素,TS 只知道是 HTMLElement
const input = document.querySelector("#name");
// input 类型是 HTMLElement | null
// 我们确信它是个 input,可以断言成更具体的类型
const inputEl = document.querySelector("#name") as HTMLInputElement;
inputEl.value = "默认值";   // HTMLInputElement 才有 value 属性

// 双重断言:两个类型完全不重叠时,先断言成 unknown 再断言
const s = "hello" as unknown as number;   // 强制(危险,慎用)

注意:断言是"逃生舱",要慎用。你骗了编译器,运行时该错还是错。能用类型守卫(下面讲)就用守卫,让 TS 自己判断;只有确实拿不到类型信息时(如 DOM、第三方数据)才用断言。

2. 守卫 1:typeof

类型守卫是通过判断逻辑让 TS 自动收窄类型的机制。最基础的是 typeof,用于判断 JS 的基础类型:

// 类型守卫 1:typeof(判断基础类型)
function padLeft(value: string | number, padding: string | number): string {
  // typeof 收窄:number 走这个分支
  if (typeof padding === "number") {
    return " ".repeat(padding) + value;
  }
  // 这里 TS 知道 padding 是 string
  return padding + value;
}

// typeof 能识别:"string" "number" "boolean" "symbol" "bigint"
//               "undefined" "object" "function"
// 注意:null 的 typeof 是 "object"(JS 历史遗留坑)

注意 typeof null"object"(JS 的历史坑),判断 null 要直接 === null

3. 守卫 2:instanceof

instanceof 判断"是不是某个类的实例",常用于 Error、Date、自定义类:

// 类型守卫 2:instanceof(判断类的实例)
class Cat { meow(): void {} }
class Dog { bark(): void {} }

function speak(animal: Cat | Dog): void {
  if (animal instanceof Cat) {
    animal.meow();    // TS 知道这里是 Cat
  } else {
    animal.bark();    // TS 知道这里是 Dog
  }
}

// instanceof 常用于判断 Error、Date、数组等内置对象的子类型
function handle(x: Date | string): number {
  if (x instanceof Date) {
    return x.getTime();   // Date 的方法
  }
  return x.length;        // string 的属性
}

4. 守卫 3:in

in 判断"对象上是否存在某个属性",适合区分结构不同的对象:

// 类型守卫 3:in(判断属性是否存在)
interface Fish { swim(): void; }
interface Bird { fly(): void; }

function move(animal: Fish | Bird): void {
  if ("swim" in animal) {
    animal.swim();    // TS 知道这里有 swim 方法
  } else {
    animal.fly();
  }
}

// in 守卫特别适合"可辨识联合"之外的对象类型判断

5. 自定义守卫:is(最强大)

当内置守卫不够用时(比如判断逻辑很复杂),你可以写一个自定义类型守卫——返回类型用 参数 is 类型 的特殊语法。这是处理复杂联合类型最强大的工具:

// 自定义类型守卫:用 "参数 is 类型" 的返回类型
type Cat2 = { meow: () => void };
type Dog2 = { bark: () => void };
type Pet = Cat2 | Dog2;

// 写一个函数,返回值类型是 "pet is Cat2"
function isCat(pet: Pet): pet is Cat2 {
  return "meow" in pet;   // 任意判断逻辑
}

function speak2(pet: Pet): void {
  if (isCat(pet)) {
    pet.meow();    // ✅ TS 知道 pet 是 Cat2
  } else {
    pet.bark();    // ✅ pet 是 Dog2
  }
}

// "pet is Cat2" 这个语法告诉 TS:
// 如果函数返回 true,那么参数 pet 就是 Cat2 类型
// 这是处理复杂联合类型最强大的守卫

关键看 pet is Cat2 这个返回类型——它告诉 TS:"这个函数返回 true 时,pet 就是 Cat2 类型"。这样在 if (isCat(pet)) 分支里,TS 自动把 pet 收窄成 Cat2

6. 断言函数 asserts(进阶)

TS 还有一类"断言函数",用 asserts 参数 is 类型 的返回类型。它和自定义守卫的区别是:守卫返回布尔值,断言函数不返回就抛异常。常用于"非空断言""类型校验":

// 断言函数:TS 5.5+ 自动推断
// 函数返回条件,TS 会自动把后续代码的类型收窄
function assertDefined<T>(value: T | undefined | null): asserts value is T {
  if (value === undefined || value === null) {
    throw new Error("值不能为空");
  }
}

const x: string | undefined = getName();
assertDefined(x);
console.log(x.length);   // ✅ 这里 x 已经是 string(断言函数收窄)

断言函数适合"不满足条件就直接报错"的校验场景,能让后续代码自动获得精确类型。

7. as vs 守卫:什么时候用哪个?

小结

类型断言 as 是你拍板,慎用;类型守卫让 TS 自动收窄,优先用。四个守卫:typeof(基础类型)、instanceof(类实例)、in(属性存在)、自定义 is(复杂逻辑)。这是处理联合类型和动态数据的全部武器。下一篇讲 TS 的项目配置 tsconfig.json

← 上一篇 TypeScript 工具类型

下一篇 TypeScript tsconfig 配置

✈️💬