TypeScript中的类型守卫 (Type Guards)
在 TypeScript 中,类型守卫 (Type Guards) 是一种用于在运行时检查变量类型,从而在特定的代码块中收窄 (Narrowing) 变量类型的机制。
简单来说,TypeScript 的静态类型分析非常强大,但它无法总是知道运行时变量的确切类型(特别是对于联合类型 Union Types)。类型守卫就是你告诉编译器:“在这个 if 块里,你可以确信这个变量是 X 类型”。
以下是 TypeScript 中几种主要的类型守卫方式:
1. typeof 关键字
这是最基本的类型守卫,利用 JavaScript 原生的 typeof 运算符。适用于基本数据类型(string, number, boolean, symbol 等)。
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) 的实例。这对于处理类继承非常有用。
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 操作符
用于检查对象中是否存在某个属性。这对于区分结构不同的对象(包括接口)非常有用。
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
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, type 或 tag),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 condition 或 asserts arg is Type
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());
}
总结:什么时候用哪个?
- 基本类型 (
string,number):使用typeof。 - 类实例:使用
instanceof。 - 简单对象属性差异:使用
in。 - 复杂的接口判断:使用 自定义类型守卫 (
is)。 - 处理多种状态或对象类型:使用 可辨识联合 (
kind属性)(这是最优雅的方式)。 - 验证输入/抛出错误:使用 断言函数 (
asserts)。
类型守卫的核心价值在于:它让你在写代码时既能享受动态语言的灵活性(联合类型),又能保持静态语言的类型安全(类型收窄)。