这篇写给前端新手。
如果你刚接触 TypeScript,看到下面这种写法时,八成会懵:
ts
(item): item is SomeType => Boolean(item.id)
这东西叫 type guard。中文可以理解成"类型守卫"或者"类型保护"。
说人话就是:
它是一个告诉 TypeScript 的方法:
"如果这个判断通过了,你就可以把这个值当成更具体的类型来用。"
先理解一个核心问题
TypeScript 经常会遇到这种情况:
ts
type User = {
id?: string;
name?: string;
};
const list: User[] = [
{ id: '1', name: 'A' },
{ name: 'B' },
];
这里 id 和 name 后面有 ?,意思是它们可能不存在。
所以 TypeScript 会很谨慎。哪怕你心里知道某个值大概率有 id,它也不会随便相信你。
没有 type guard 时会怎样
ts
const validUsers = list.filter((item) => Boolean(item.id));
这行代码在运行时没问题,它确实会把没有 id 的项过滤掉。
但是 TypeScript 往往仍然认为:
ts
validUsers[0].id
还是"可能是 undefined"。
为什么?
因为它只看到你写了一个普通布尔判断,它不一定能完全推断出:
"过滤完以后,这个数组里的每一项都一定有 id。"
type guard 是怎么帮忙的
这时候可以这样写:
ts
const validUsers = list.filter(
(item): item is { id: string; name?: string } => Boolean(item.id)
);
这里最重要的是:
ts
item is { id: string; name?: string }
这句话是对 TypeScript 说的,不是给浏览器看的。
它的意思是:
"如果这个函数返回 true,那 item 就可以被当成一个 id 一定存在的对象。"
这样后面你再访问:
ts
validUsers[0].id
TypeScript 就会更放心,因为它知道这里的 id 已经被"缩窄"成 string 了。
它和普通判断有什么区别
看下面这两部分:
ts
Boolean(item.id)
这是运行时判断。程序真正执行的是它。
ts
item is { id: string; name?: string }
这是类型提示。它主要是写给 TypeScript 编译器看的。
所以可以把一整句拆开理解:
- 前半句负责"真的检查"
- 后半句负责"告诉 TypeScript 检查完以后该怎么看这个值"
什么时候 type guard 很有用
最常见是下面这几类情况:
- 一个字段是可选的,你过滤后想让 TypeScript 知道它现在一定存在。
- 一个值可能是多种类型中的一种,你想先判断再安全使用。
- 你在
filter、find、if里做了检查,但 TypeScript 还不够确定。
比如:
ts
type Data = string | number;
function printLength(value: Data) {
if (typeof value === 'string') {
console.log(value.length);
}
}
这里 typeof value === 'string' 其实也是一种 type guard。
它告诉 TypeScript:
"进到这个 if 里面后,value 就是字符串。"
什么时候它是多余的
如果一个值本来就已经是那个类型了,那再写一遍 type guard 就没有太大意义。
比如:
ts
type Category = {
frameworkId: string;
categoryId: string;
};
const categories: Category[] = [];
这里数组里的每一项本来就是:
ts
{
frameworkId: string;
categoryId: string;
}
那你再写:
ts
(item): item is Category => Boolean(item.frameworkId && item.categoryId)
就有点像在说:
"如果判断通过,那它就是它自己。"
这时 reviewer 往往会觉得这段写法太重了,因为它没有提供新的类型信息。
一句最实用的判断标准
你可以这样问自己:
"我写这个 type guard 之后,TypeScript 知道的类型信息,比之前更多了吗?"
如果答案是:
- 是,那它通常有价值
- 不是,那它大概率只是写复杂了
新手可以先记住这三种
1. typeof
ts
if (typeof value === 'string') {
// 这里 value 是 string
}
2. instanceof
ts
if (error instanceof Error) {
// 这里 error 是 Error
}
3. 自定义 item is Xxx
ts
const result = list.filter(
(item): item is { id: string } => Boolean(item.id)
);
这个最灵活,但也是最容易被滥用的。
最后一句总结
type guard 不是为了让代码"看起来更高级",而是为了在类型不够明确的时候,帮 TypeScript 正确缩窄类型。
如果原来的类型已经很明确,再加一个 type guard,通常只会让代码更绕。