在写前端项目时,经常会遇到一个变量可能是多种类型的情况。比如从接口返回的数据,某个字段可能是字符串,也可能是对象,甚至可能是 null。这时候如果不小心处理,运行时就容易出错。
TypeScript 虽然能帮我们提前发现一些类型问题,但有时候类型太宽泛,光靠静态检查还不够。这时候就需要“类型守卫”来帮忙,在运行时确认具体类型,让后续操作更安全。
什么是类型守卫
类型守卫其实就是一个函数或表达式,它的作用是告诉 TypeScript:“放心,我现在确定这个值是什么类型”。一旦通过了这个“检查”,TS 就会在接下来的代码块里按对应的类型来处理。
最常见的例子是联合类型的判断。比如有个函数接收 number 或 string 类型的参数,你想对它们分别处理:
function printValue(value: number | string) {
if (typeof value === 'string') {
// 这里 TypeScript 知道 value 一定是 string
console.log(value.toUpperCase());
} else {
// 这里一定是 number
console.log(value.toFixed(2));
}
}
这里的 typeof value === 'string' 就是一个类型守卫。TypeScript 能识别这种常见的判断方式,并自动缩小类型范围。
自定义类型守卫函数
有些情况下,typeof 不够用。比如你面对的是多个对象类型组成的联合类型,就得自己写判断逻辑。
假设你在做一个表单校验功能,有两种数据结构:用户信息和订单信息。你可以这样定义:
interface User {
name: string;
age: number;
}
interface Order {
orderId: string;
amount: number;
}
type FormData = User | Order;
现在要写个函数判断到底是哪种类型。可以定义一个类型守卫函数:
function isUser(data: FormData): data is User {
return (data as User).name !== undefined;
}
注意返回值写法:data is User,这是关键。它告诉编译器:如果这个函数返回 true,那 data 就一定是 User 类型。
使用起来就很顺手:
function handleFormData(data: FormData) {
if (isUser(data)) {
// 此时可以直接访问 name 和 age
console.log('欢迎你,' + data.name);
} else {
// 反之就是 Order
console.log('订单金额:' + data.amount);
}
}
用 in 操作符做类型区分
除了 typeof 和自定义函数,还可以用 in 操作符来判断属性是否存在,这也是一种类型守卫。
function process(input: User | Order) {
if ('name' in input) {
console.log('这是一个用户:', input.name);
} else {
console.log('这是一个订单:', input.orderId);
}
}
这种写法简洁直观,适合区分具有不同字段的对象。
实战小场景
想象你在做一个记账小程序,从本地存储读取数据时,可能拿到旧格式(只有金额)或新格式(带分类)。你可以用类型守卫来兼容两种结构:
interface OldRecord {
money: number;
}
interface NewRecord {
money: number;
category: string;
}
function isNewRecord(r: any): r is NewRecord {
return 'category' in r;
}
// 使用
const record: OldRecord | NewRecord = JSON.parse(localStorage.getItem('last'));
if (isNewRecord(record)) {
showWithCategory(record.category, record.money);
} else {
showSimple(record.money);
}
这样即使数据来源不可控,也能保证程序不会因为访问不存在的字段而崩溃。