一、类型守卫是什么?

在TypeScript的世界里,类型守卫就像是一个智能门卫,它能帮你在代码运行时判断一个变量到底属于哪种具体类型。想象一下,你有一个可能是字符串也可能是数字的变量,而类型守卫就是那个能帮你准确分辨它真实身份的工具。

举个例子:

function printValue(value: string | number) {
  if (typeof value === 'string') {
    // 这里value被确定为string类型
    console.log(value.toUpperCase()); // 可以安全调用字符串方法
  } else {
    // 这里value被确定为number类型  
    console.log(value.toFixed(2)); // 可以安全调用数字方法
  }
}

这个简单的typeof检查就是最基础的类型守卫。它会根据运行时检查结果,自动缩小变量的类型范围,让你的代码更安全。

二、常见的类型守卫方式

1. typeof守卫

最直接的方式,适合处理基本类型(string、number等):

function handleValue(val: string | number) {
  if (typeof val === 'string') {
    return val.trim(); // 明确是字符串
  }
  return val.toFixed(); // 明确是数字
}

2. instanceof守卫

处理自定义类或内置对象时特别有用:

class Dog { bark() { console.log('汪!') } }
class Cat { meow() { console.log('喵~') } }

function play(pet: Dog | Cat) {
  if (pet instanceof Dog) {
    pet.bark(); // 类型确定为Dog
  } else {
    pet.meow(); // 类型确定为Cat
  }
}

3. 自定义类型谓词

当内置方法不够用时,可以自己定义类型判断函数:

interface Fish { swim(): void }
interface Bird { fly(): void }

function isFish(pet: Fish | Bird): pet is Fish {
  return (pet as Fish).swim !== undefined;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    pet.swim(); // 类型确定为Fish
  } else {
    pet.fly(); // 类型确定为Bird
  }
}

注意pet is Fish这个返回值类型声明,它告诉TypeScript这个函数是个类型守卫。

三、进阶应用场景

1. 处理联合类型

当API返回复杂数据时特别实用:

type APIResponse = 
  | { status: 'success'; data: string[] }
  | { status: 'error'; message: string };

function handleResponse(response: APIResponse) {
  if (response.status === 'success') {
    console.log(response.data.join(', ')); // 确定是成功响应
  } else {
    console.error(response.message); // 确定是错误响应
  }
}

2. 配合数组过滤使用

const mixedArray = [1, 'hello', 2, 'world', true];

// 过滤出所有字符串
const stringsOnly = mixedArray.filter((item): item is string => {
  return typeof item === 'string';
});

stringsOnly.forEach(str => {
  console.log(str.toUpperCase()); // 安全操作,因为类型确定为string[]
});

四、技术细节与注意事项

  1. 性能考量:简单的typeof检查几乎没有性能开销,但复杂的自定义守卫可能会影响性能,特别是在热路径代码中。

  2. 过度使用警告:不要为了类型安全而把代码变成全是类型检查的"防御性编程",找到平衡点很重要。

  3. 与any类型的交互:类型守卫是逃离any类型的重要方式之一:

function processValue(val: any) {
  if (typeof val === 'string') {
    // 现在val从any缩小为string
    console.log(val.length);
  }
}
  1. 浏览器兼容性:某些特殊场景下(如区分null和object),不同浏览器的typeof行为可能不一致,需要注意。

五、最佳实践总结

  1. 优先使用简单的typeof和instanceof守卫
  2. 复杂逻辑封装成自定义类型谓词
  3. 联合类型配合判别属性(discriminant)是绝配
  4. 数组过滤时显式声明类型谓词
  5. 避免在类型守卫中做有副作用的操作

类型守卫是TypeScript类型系统的精髓之一,它让静态类型检查有了动态的灵活性。掌握好这个特性,能让你的代码既安全又优雅,就像给你的程序装上了智能导航,既不会迷路又能选择最优路径。