Skip to content
第 12 章 前端 ⏱ 10 分钟阅读

第 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/undefinedif (x)
asserts抛错断言asserts x is T

动手练习 ​

  1. typeof 守卫:写 process(x: string \| number),字符串转大写、数字保留 2 位小数
  2. 可辨识联合:实现 handle(event: ClickEvent \| KeyEvent),根据 type 区分
  3. 自定义守卫:写 isString(x: unknown): x is string,用 typeof 实现

下一章:第 13 章:工具类型 →

本站基于 VitePress 构建 · 由 StackHub 团队维护