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 守卫:什么时候用哪个?
- 优先用守卫(typeof/instanceof/in/自定义 is):让 TS 自己判断,安全。
- 拿不到运行时信息时用 as:比如 DOM 元素、
JSON.parse的结果、第三方无类型的数据。用之前最好做运行时校验(如 zod)。 - 双重断言
as unknown as X:几乎一定是错的,只在不得已时用。
小结
类型断言 as 是你拍板,慎用;类型守卫让 TS 自动收窄,优先用。四个守卫:typeof(基础类型)、instanceof(类实例)、in(属性存在)、自定义 is(复杂逻辑)。这是处理联合类型和动态数据的全部武器。下一篇讲 TS 的项目配置 tsconfig.json。
← 上一篇 TypeScript 工具类型
下一篇 TypeScript tsconfig 配置 →