第 12 章:类型守卫
学习目标
- 用 typeof / instanceof 收窄类型
- 学会自定义类型守卫函数
- 掌握 in 操作符与可辨识联合
- 理解类型谓词
is
一、typeof 守卫(原始类型)
typescript
function print(value: string | number): void {
if (typeof value === 'string') {
console.log(value.toUpperCase()); // ✅ 收窄为 string
} else {
console.log(value.toFixed(2)); // ✅ 收窄为 number
}
}typeof 支持的类型:
typescript
typeof x === 'string' | 'number' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | 'bigint'⚠️ 坑 1:
typeof null === 'object',所以判断 null 要单独x === null。
二、instanceof 守卫(类实例)
typescript
class Dog { bark() {} }
class Cat { meow() {} }
function speak(animal: Dog | Cat): void {
if (animal instanceof Dog) {
animal.bark(); // ✅
} else {
animal.meow(); // ✅ 收窄为 Cat
}
}三、in 操作符守卫(属性存在)
typescript
interface Fish { swim(): void }
interface Bird { fly(): void }
function move(animal: Fish | Bird): void {
if ('swim' in animal) {
animal.swim(); // ✅ 收窄为 Fish
} else {
animal.fly(); // ✅ 收窄为 Bird
}
}四、自定义类型守卫(类型谓词 is)
核心语法:parameterName is Type
typescript
interface Fish { swim(): void }
interface Bird { fly(): void }
function isFish(animal: Fish | Bird): animal is Fish {
return (animal as Fish).swim !== undefined;
}
function move(animal: Fish | Bird): void {
if (isFish(animal)) {
animal.swim(); // ✅ 收窄为 Fish
} else {
animal.fly(); // ✅ 收窄为 Bird
}
}⚠️ 坑 2:
is类型谓词只在编译期有效,运行期需要isFish函数真返回 boolean。如果函数返回错了,运行时不报错但 TS 推导会"骗"你。
五、可辨识联合(Discriminated Union)
用共同的字面量字段区分:
typescript
type Shape =
| { kind: 'circle'; r: number }
| { kind: 'rect'; w: number; h: number }
| { kind: 'square'; size: number };
function area(s: Shape): number {
switch (s.kind) {
case 'circle':
return Math.PI * s.r ** 2; // ✅ 收窄到 circle
case 'rect':
return s.w * s.h; // ✅ 收窄到 rect
case 'square':
return s.size ** 2; // ✅ 收窄到 square
}
}⚠️ 坑 3:
switch必须穷尽,否则编译器会提醒"Function lacks ending return statement"。用never兜底:
typescript
default:
const _: never = s; // 漏 case 编译报错
return _;六、真值收窄(Truthiness Narrowing)
typescript
function print(value: string | null | undefined): void {
if (value) {
console.log(value.toUpperCase()); // ✅ 收窄为 string
}
}避免 falsy 陷阱:空字符串、0、NaN 也会被过滤,可能漏掉合法值。
typescript
function process(x: number | null): void {
if (x) { // ⚠️ 0 会被过滤
console.log(x.toFixed());
}
}
// 明确判断 null
function process(x: number | null): void {
if (x !== null) { // ✅
console.log(x.toFixed());
}
}七、相等性收窄
typescript
function compare(x: string | number, y: string | boolean): void {
if (x === y) {
// x 和 y 都是 string
x.toUpperCase();
y.toUpperCase();
}
}八、断言函数(asserts)
typescript
function assertDefined<T>(value: T): asserts value is NonNullable<T> {
if (value === null || value === undefined) {
throw new Error('Must not be null');
}
}
function process(value: string | null): void {
assertDefined(value);
value.toUpperCase(); // ✅ 已断言不为 null
}九、实战:API 响应处理
typescript
interface Success<T> { ok: true; data: T }
interface Failure { ok: false; error: string }
type Result<T> = Success<T> | Failure;
function isSuccess<T>(r: Result<T>): r is Success<T> {
return r.ok;
}
function handle<T>(r: Result<T>): string {
if (isSuccess(r)) {
return `成功:${JSON.stringify(r.data)}`;
} else {
return `失败:${r.error}`;
}
}十、本章小结
| 守卫方式 | 适用场景 | 关键字 |
|---|---|---|
| typeof | 原始类型 | typeof x === 'string' |
| instanceof | 类实例 | x instanceof Date |
| in | 对象属性 | 'name' in obj |
| 类型谓词 | 自定义 | arg is Type |
| 可辨识联合 | 多分支对象 | kind: 'xxx' |
| 真值 | 过滤 null/undefined | if (x) |
| asserts | 抛错断言 | asserts x is T |
动手练习
- typeof 守卫:写
process(x: string \| number),字符串转大写、数字保留 2 位小数 - 可辨识联合:实现
handle(event: ClickEvent \| KeyEvent),根据type区分 - 自定义守卫:写
isString(x: unknown): x is string,用typeof实现
下一章:第 13 章:工具类型 →