你将掌握类型收窄的各种机制,并编写自己的谓词,让联合类型更加可靠。
在 Kodokon 中打开本课类型收窄(Narrowing)是 TypeScript 通过分析控制流,将一个宽泛的类型(联合类型、unknown)细化为更精确类型的机制。你其实一直在不知不觉中使用它:每一个作用于已类型化值的 if 都会触发一次分析。编译器会跟踪三类内置守卫:用于原始类型的 typeof、用于类实例的 instanceof,以及用于判断属性存在与否的 in。恰当地选用它们,能让你免于使用 as 的鲁莽类型断言,因为断言是关闭校验,而不是引导校验。
function formatValue(
value: string | number | Date
): string {
if (typeof value === "string") {
return value.trim();
}
if (value instanceof Date) {
return value.toISOString();
}
return value.toFixed(2);
}当你处理结构不同、又没有区分字段的对象时,in 运算符极为宝贵。但要小心:in 测试的是某个键是否存在(包括继承而来的键),而不是它的类型。对于外部数据(API、JSON),它是根据你的声明来收窄,而不是根据运行时实际存在的内容。一个经典的现实陷阱:typeof value === "object" 的检查会放过 null,因为在 JavaScript 中 typeof null 就是 "object"。请始终额外加上明确的 null 检查。
interface Cat { meow(): void }
interface Dog { bark(): void }
function speak(animal: Cat | Dog): void {
if ("meow" in animal) {
animal.meow();
} else {
animal.bark();
}
}当守卫逻辑变得可复用,或过于复杂而超出流程分析的能力时,就编写一个自定义类型守卫:一个返回类型标注为 value is T 的函数。编译器随后会信任你的实现。这是一份契约:如果你的检查不完整,就会重新引入类型系统永远看不到的运行时错误。自 TypeScript 5.5 起,简单谓词会在诸如 filter 的回调中被自动推断,但对于导出的函数而言,显式标注仍是惯例。
interface User { id: string; email: string }
function isUser(value: unknown): value is User {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
typeof value.id === "string" &&
"email" in value &&
typeof value.email === "string"
);
}
const raw: unknown =
JSON.parse('{"id":"1","email":"a@b.c"}');
if (isUser(raw)) {
console.log(raw.email.toLowerCase());
}typeof value === "object" 的检查不足以收窄为非 null 对象?booleanvalue as Uservalue is Userasserts value