类型保护与类型守卫

什么是类型保护与类型守卫

在TypeScript中,类型保护(Type Guards)和类型守卫是帮助我们在代码中缩小变量类型范围的机制。它们允许TypeScript编译器根据某些条件判断来推断出更具体的类型,从而在特定代码块中提供更精确的类型检查。

为什么需要类型保护

JavaScript是动态类型语言,而TypeScript为其添加了静态类型系统。但在实际开发中,我们经常需要处理可能是多种类型的变量。例如:

typescript 复制代码
function printLength(value: string | number) {
  // 这里value可能是string或number
  console.log(value.length); // 错误:number类型没有length属性
}

这时就需要类型保护来告诉TypeScript在特定条件下变量的确切类型。

常见的类型保护方式

1. typeof类型保护

使用typeof操作符可以检查基本类型:

typescript 复制代码
function printLength(value: string | number) {
  if (typeof value === 'string') {
    console.log(value.length); // 这里value被推断为string
  } else {
    console.log(value.toFixed(2)); // 这里value被推断为number
  }
}

2. instanceof类型保护

对于类实例,可以使用instanceof

typescript 复制代码
class Bird {
  fly() {
    console.log('Flying');
  }
}

class Fish {
  swim() {
    console.log('Swimming');
  }
}

function move(animal: Bird | Fish) {
  if (animal instanceof Bird) {
    animal.fly(); // 这里animal被推断为Bird
  } else {
    animal.swim(); // 这里animal被推断为Fish
  }
}

3. in操作符类型保护

检查对象是否具有特定属性:

typescript 复制代码
interface Dog {
  bark(): void;
}

interface Cat {
  meow(): void;
}

function makeSound(pet: Dog | Cat) {
  if ('bark' in pet) {
    pet.bark(); // 这里pet被推断为Dog
  } else {
    pet.meow(); // 这里pet被推断为Cat
  }
}

4. 自定义类型保护函数

可以创建返回类型谓词的自定义函数:

typescript 复制代码
function isString(value: any): value is string {
  return typeof value === 'string';
}

function example(value: string | number) {
  if (isString(value)) {
    console.log(value.length); // value被推断为string
  } else {
    console.log(value.toFixed(2)); // value被推断为number
  }
}

类型守卫的高级用法

可辨识联合(Discriminated Unions)

当联合类型中的每个成员都有一个共同的属性(通常是字面量类型)时,可以使用可辨识联合:

typescript 复制代码
interface Square {
  kind: 'square';
  size: number;
}

interface Rectangle {
  kind: 'rectangle';
  width: number;
  height: number;
}

type Shape = Square | Rectangle;

function area(shape: Shape) {
  switch (shape.kind) {
    case 'square':
      return shape.size * shape.size; // shape被推断为Square
    case 'rectangle':
      return shape.width * shape.height; // shape被推断为Rectangle
  }
}

非空断言

使用!操作符可以告诉TypeScript某个值不为null或undefined:

typescript 复制代码
function fixed(name: string | null) {
  console.log(name!.toUpperCase()); // 非空断言
}

最佳实践

  1. 优先使用内置的类型保护(typeof, instanceof, in)
  2. 对于复杂类型,创建自定义类型保护函数
  3. 使用可辨识联合模式处理复杂的联合类型
  4. 谨慎使用非空断言,确保逻辑上确实不会为null/undefined

类型保护与类型守卫是TypeScript强大类型系统的关键特性之一,合理使用可以显著提高代码的类型安全性和开发体验。