Skip to content

类型守卫

概述

类型守卫(Type Guards)是 TypeScript 中一种强大的类型缩小(Type Narrowing)机制,它允许你在运行时检查值的类型,并让 TypeScript 编译器在相应的代码块中自动推断出更具体的类型。类型守卫通过运行时检查来确保类型安全,比类型断言更安全可靠。TypeScript 支持多种类型守卫方式:typeof 操作符、instanceof 操作符、in 操作符,以及自定义类型守卫函数。类型守卫是处理联合类型、处理 unknown 类型、确保类型安全的最佳实践。

基本概念

类型守卫的核心思想是:通过运行时检查来缩小类型范围。当 TypeScript 编译器遇到类型守卫时,它会在相应的代码块中将类型缩小为更具体的类型。

typescript
// 联合类型
type StringOrNumber = string | number;

function process(value: StringOrNumber): void {
  // 使用 typeof 类型守卫
  if (typeof value === "string") {
    // 在这个代码块中,TypeScript 知道 value 是 string
    console.log(value.toUpperCase());  // ✅ 可以安全调用
  } else {
    // 在这个代码块中,TypeScript 知道 value 是 number
    console.log(value.toFixed(2));     // ✅ 可以安全调用
  }
}

typeof 类型守卫

typeof 操作符是最常用的类型守卫之一,用于检查基本类型。

基本用法

typescript
// 使用 typeof 检查基本类型
function processValue(value: string | number | boolean): void {
  if (typeof value === "string") {
    // TypeScript 知道这里是 string
    console.log(value.length);
    console.log(value.toUpperCase());
  } else if (typeof value === "number") {
    // TypeScript 知道这里是 number
    console.log(value.toFixed(2));
    console.log(value * 2);
  } else {
    // TypeScript 知道这里是 boolean
    console.log(value ? "真" : "假");
  }
}

// 使用示例
processValue("hello");    // 输出: 5, HELLO
processValue(42);         // 输出: 42.00, 84
processValue(true);       // 输出: 真

typeof 支持的类型

typeof 可以检查以下类型:

typescript
function checkType(value: unknown): void {
  if (typeof value === "string") {
    // string
  } else if (typeof value === "number") {
    // number
  } else if (typeof value === "boolean") {
    // boolean
  } else if (typeof value === "undefined") {
    // undefined
  } else if (typeof value === "object") {
    // object(注意:null 也是 "object")
  } else if (typeof value === "function") {
    // function
  } else if (typeof value === "symbol") {
    // symbol
  } else if (typeof value === "bigint") {
    // bigint
  }
}

注意

typeof null 返回 "object",这是 JavaScript 的历史遗留问题。要检查 null,应该直接使用 value === nullvalue !== null

处理 null 和 undefined

typescript
// 检查 null
function processNullable(value: string | null): void {
  if (value === null) {
    // TypeScript 知道这里是 null
    console.log("值为 null");
  } else {
    // TypeScript 知道这里是 string
    console.log(value.toUpperCase());
  }
}

// 检查 undefined
function processOptional(value: string | undefined): void {
  if (value === undefined) {
    // TypeScript 知道这里是 undefined
    console.log("值为 undefined");
  } else {
    // TypeScript 知道这里是 string
    console.log(value.length);
  }
}

// 同时检查 null 和 undefined
function processMaybe(value: string | null | undefined): void {
  if (value == null) {  // 使用 == 可以同时检查 null 和 undefined
    // TypeScript 知道这里是 null | undefined
    console.log("值为 null 或 undefined");
  } else {
    // TypeScript 知道这里是 string
    console.log(value.toUpperCase());
  }
}

// 或者使用非空断言(如果确定不为空)
function processDefinite(value: string | null | undefined): void {
  if (value != null) {  // 使用 != 可以同时检查 null 和 undefined
    // TypeScript 知道这里是 string
    console.log(value.toUpperCase());
  }
}

instanceof 类型守卫

instanceof 操作符用于检查对象是否是某个类的实例。

基本用法

typescript
// 定义类
class Dog {
  name: string;
  breed: string;
  
  constructor(name: string, breed: string) {
    this.name = name;
    this.breed = breed;
  }
  
  bark(): void {
    console.log("Woof!");
  }
}

class Cat {
  name: string;
  color: string;
  
  constructor(name: string, color: string) {
    this.name = name;
    this.color = color;
  }
  
  meow(): void {
    console.log("Meow!");
  }
}

// 使用 instanceof 类型守卫
function handlePet(pet: Dog | Cat): void {
  if (pet instanceof Dog) {
    // TypeScript 知道这里是 Dog
    pet.bark();        // ✅ 可以调用
    console.log(pet.breed);  // ✅ 可以访问 breed
  } else {
    // TypeScript 知道这里是 Cat
    pet.meow();       // ✅ 可以调用
    console.log(pet.color);  // ✅ 可以访问 color
  }
}

// 使用示例
const dog = new Dog("Buddy", "Golden Retriever");
const cat = new Cat("Whiskers", "Orange");

handlePet(dog);  // 输出: Woof!, Golden Retriever
handlePet(cat);  // 输出: Meow!, Orange

处理内置类型

typescript
// 处理 Date 对象
function processDate(value: Date | string): void {
  if (value instanceof Date) {
    // TypeScript 知道这里是 Date
    console.log(value.getFullYear());
    console.log(value.toISOString());
  } else {
    // TypeScript 知道这里是 string
    console.log(value.toUpperCase());
  }
}

// 处理数组
function processArray(value: unknown): void {
  if (value instanceof Array) {
    // TypeScript 知道这里是 Array
    console.log(value.length);
    console.log(value.map(x => x));
  } else {
    // TypeScript 不知道具体类型
    console.log("不是数组");
  }
}

// 处理正则表达式
function processRegex(value: RegExp | string): void {
  if (value instanceof RegExp) {
    // TypeScript 知道这里是 RegExp
    console.log(value.test("test"));
    console.log(value.source);
  } else {
    // TypeScript 知道这里是 string
    console.log(value.length);
  }
}

in 操作符类型守卫

in 操作符用于检查对象是否包含某个属性。

基本用法

typescript
// 定义接口
interface Dog {
  name: string;
  breed: string;
  bark(): void;
}

interface Cat {
  name: string;
  color: string;
  meow(): void;
}

// 使用 in 操作符检查属性
function handleAnimal(animal: Dog | Cat): void {
  if ("breed" in animal) {
    // TypeScript 知道这里是 Dog(因为有 breed 属性)
    animal.bark();        // ✅ 可以调用
    console.log(animal.breed);
  } else {
    // TypeScript 知道这里是 Cat(因为没有 breed 属性)
    animal.meow();         // ✅ 可以调用
    console.log(animal.color);
  }
}

// 使用示例
const dog: Dog = {
  name: "Buddy",
  breed: "Golden Retriever",
  bark: () => console.log("Woof!")
};

const cat: Cat = {
  name: "Whiskers",
  color: "Orange",
  meow: () => console.log("Meow!")
};

handleAnimal(dog);  // 输出: Woof!, Golden Retriever
handleAnimal(cat);  // 输出: Meow!, Orange

处理可选属性

typescript
interface User {
  id: number;
  name: string;
  email?: string;  // 可选属性
}

function processUser(user: User): void {
  if ("email" in user) {
    // TypeScript 知道 user 有 email 属性(但可能是 undefined)
    if (user.email) {
      // 进一步检查确保不是 undefined
      console.log(user.email.toUpperCase());
    }
  } else {
    // TypeScript 知道 user 没有 email 属性
    console.log("用户没有邮箱");
  }
}

自定义类型守卫

自定义类型守卫是使用类型谓词(Type Predicate)的函数,格式为 parameterName is Type

基本语法

typescript
// 自定义类型守卫函数
function isString(value: unknown): value is string {
  return typeof value === "string";
}

// 使用自定义类型守卫
function process(value: unknown): void {
  if (isString(value)) {
    // TypeScript 知道这里是 string
    console.log(value.toUpperCase());
    console.log(value.length);
  } else {
    // TypeScript 不知道具体类型
    console.log("不是字符串");
  }
}

类型谓词语法

类型守卫函数必须返回类型谓词(parameterName is Type):

typescript
// ✅ 正确:返回类型谓词
function isNumber(value: unknown): value is number {
  return typeof value === "number";
}

// ❌ 错误:只返回 boolean
function isNumberWrong(value: unknown): boolean {
  return typeof value === "number";
  // 这样 TypeScript 不会进行类型缩小
}

复杂类型守卫示例

typescript
// 定义类型
interface User {
  id: number;
  name: string;
  email: string;
}

interface Admin {
  id: number;
  name: string;
  role: "admin";
  permissions: string[];
}

// 自定义类型守卫:检查是否为 User
function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "id" in value &&
    "name" in value &&
    "email" in value &&
    typeof (value as any).id === "number" &&
    typeof (value as any).name === "string" &&
    typeof (value as any).email === "string"
  );
}

// 自定义类型守卫:检查是否为 Admin
function isAdmin(value: unknown): value is Admin {
  if (!isUser(value)) {
    return false;
  }
  
  return (
    "role" in value &&
    "permissions" in value &&
    (value as any).role === "admin" &&
    Array.isArray((value as any).permissions)
  );
}

// 使用自定义类型守卫
function processUser(value: unknown): void {
  if (isAdmin(value)) {
    // TypeScript 知道这里是 Admin
    console.log(`管理员: ${value.name}`);
    console.log(`权限: ${value.permissions.join(", ")}`);
  } else if (isUser(value)) {
    // TypeScript 知道这里是 User
    console.log(`用户: ${value.name}`);
    console.log(`邮箱: ${value.email}`);
  } else {
    console.log("无效的用户数据");
  }
}

处理联合类型的类型守卫

typescript
// 定义联合类型
type Response = 
  | { status: "success"; data: User }
  | { status: "error"; message: string };

interface User {
  id: number;
  name: string;
}

// 类型守卫:检查是否为成功响应
function isSuccessResponse(response: Response): response is { status: "success"; data: User } {
  return response.status === "success";
}

// 类型守卫:检查是否为错误响应
function isErrorResponse(response: Response): response is { status: "error"; message: string } {
  return response.status === "error";
}

// 使用类型守卫处理响应
function handleResponse(response: Response): void {
  if (isSuccessResponse(response)) {
    // TypeScript 知道这里是成功响应
    console.log(`用户: ${response.data.name}`);
    console.log(`ID: ${response.data.id}`);
  } else if (isErrorResponse(response)) {
    // TypeScript 知道这里是错误响应
    console.error(`错误: ${response.message}`);
  }
}

// 使用示例
const successResponse: Response = {
  status: "success",
  data: { id: 1, name: "John" }
};

const errorResponse: Response = {
  status: "error",
  message: "用户不存在"
};

handleResponse(successResponse);  // 输出: 用户: John, ID: 1
handleResponse(errorResponse);    // 输出: 错误: 用户不存在

使用示例

示例 1:处理 API 响应

typescript
// 定义 API 响应类型
type ApiResponse<T> = 
  | { success: true; data: T }
  | { success: false; error: string };

interface User {
  id: number;
  name: string;
  email: string;
}

// 类型守卫:检查是否为成功响应
function isSuccessResponse<T>(response: ApiResponse<T>): response is { success: true; data: T } {
  return response.success === true;
}

// 处理 API 响应
async function fetchUser(id: number): Promise<ApiResponse<User>> {
  // 模拟 API 调用
  return {
    success: true,
    data: {
      id: 1,
      name: "John Doe",
      email: "john@example.com"
    }
  };
}

// 使用类型守卫安全处理响应
async function getUser(id: number): Promise<User | null> {
  const response = await fetchUser(id);
  
  if (isSuccessResponse(response)) {
    // TypeScript 知道这里是成功响应
    return response.data;  // ✅ 类型安全
  } else {
    // TypeScript 知道这里是错误响应
    console.error(response.error);
    return null;
  }
}

示例 2:处理表单数据

typescript
// 定义表单数据类型
type FormData = 
  | { type: "text"; value: string }
  | { type: "number"; value: number }
  | { type: "checkbox"; value: boolean };

// 类型守卫:检查是否为文本类型
function isTextFormData(data: FormData): data is { type: "text"; value: string } {
  return data.type === "text";
}

// 类型守卫:检查是否为数字类型
function isNumberFormData(data: FormData): data is { type: "number"; value: number } {
  return data.type === "number";
}

// 处理表单数据
function processFormData(data: FormData): void {
  if (isTextFormData(data)) {
    // TypeScript 知道这里是文本类型
    console.log(`文本: ${data.value.toUpperCase()}`);
  } else if (isNumberFormData(data)) {
    // TypeScript 知道这里是数字类型
    console.log(`数字: ${data.value.toFixed(2)}`);
  } else {
    // TypeScript 知道这里是复选框类型
    console.log(`复选框: ${data.value ? "选中" : "未选中"}`);
  }
}

示例 3:处理 DOM 元素

typescript
// 类型守卫:检查是否为 HTMLInputElement
function isInputElement(element: HTMLElement | null): element is HTMLInputElement {
  return element !== null && element instanceof HTMLInputElement;
}

// 类型守卫:检查是否为 HTMLButtonElement
function isButtonElement(element: HTMLElement | null): element is HTMLButtonElement {
  return element !== null && element instanceof HTMLButtonElement;
}

// 处理 DOM 元素
function handleElement(element: HTMLElement | null): void {
  if (isInputElement(element)) {
    // TypeScript 知道这里是 HTMLInputElement
    console.log(`输入值: ${element.value}`);
    console.log(`类型: ${element.type}`);
  } else if (isButtonElement(element)) {
    // TypeScript 知道这里是 HTMLButtonElement
    console.log(`按钮文本: ${element.textContent}`);
    console.log(`是否禁用: ${element.disabled}`);
  } else if (element === null) {
    console.log("元素不存在");
  } else {
    console.log("其他类型的元素");
  }
}

示例 4:处理 JSON 数据

typescript
// 定义数据结构
interface Config {
  apiUrl: string;
  timeout: number;
  retries: number;
}

// 类型守卫:检查是否为有效的配置对象
function isConfig(value: unknown): value is Config {
  return (
    typeof value === "object" &&
    value !== null &&
    "apiUrl" in value &&
    "timeout" in value &&
    "retries" in value &&
    typeof (value as any).apiUrl === "string" &&
    typeof (value as any).timeout === "number" &&
    typeof (value as any).retries === "number"
  );
}

// 安全解析 JSON
function parseConfig(jsonString: string): Config | null {
  try {
    const parsed = JSON.parse(jsonString);
    
    if (isConfig(parsed)) {
      // TypeScript 知道这里是 Config
      return parsed;  // ✅ 类型安全
    }
    
    return null;
  } catch {
    return null;
  }
}

// 使用示例
const configJson = `{
  "apiUrl": "https://api.example.com",
  "timeout": 5000,
  "retries": 3
}`;

const config = parseConfig(configJson);
if (config) {
  // TypeScript 知道 config 是 Config 类型
  console.log(config.apiUrl);    // ✅ 可以访问
  console.log(config.timeout);   // ✅ 可以访问
  console.log(config.retries);   // ✅ 可以访问
}

类型守卫的组合使用

可以组合使用多种类型守卫来实现更复杂的类型检查:

typescript
// 定义类型
type Value = string | number | boolean | null | undefined;

// 组合使用多种类型守卫
function processValue(value: Value): void {
  // 先检查 null 和 undefined
  if (value == null) {
    console.log("值为 null 或 undefined");
    return;
  }
  
  // 然后使用 typeof 检查类型
  if (typeof value === "string") {
    console.log(`字符串: ${value.toUpperCase()}`);
  } else if (typeof value === "number") {
    console.log(`数字: ${value.toFixed(2)}`);
  } else if (typeof value === "boolean") {
    console.log(`布尔值: ${value ? "真" : "假"}`);
  }
}

// 组合自定义类型守卫
interface Animal {
  name: string;
}

interface Dog extends Animal {
  breed: string;
  bark(): void;
}

interface Cat extends Animal {
  color: string;
  meow(): void;
}

// 类型守卫:检查是否为 Animal
function isAnimal(value: unknown): value is Animal {
  return (
    typeof value === "object" &&
    value !== null &&
    "name" in value &&
    typeof (value as any).name === "string"
  );
}

// 类型守卫:检查是否为 Dog
function isDog(value: unknown): value is Dog {
  if (!isAnimal(value)) {
    return false;
  }
  
  return (
    "breed" in value &&
    "bark" in value &&
    typeof (value as any).breed === "string" &&
    typeof (value as any).bark === "function"
  );
}

// 使用组合的类型守卫
function handleAnimal(value: unknown): void {
  if (!isAnimal(value)) {
    console.log("不是动物");
    return;
  }
  
  // TypeScript 知道这里是 Animal
  console.log(`动物名称: ${value.name}`);
  
  if (isDog(value)) {
    // TypeScript 知道这里是 Dog
    value.bark();
    console.log(`品种: ${value.breed}`);
  } else {
    // TypeScript 知道这里是其他动物(可能是 Cat)
    console.log("不是狗");
  }
}

类型守卫 vs 类型断言

类型守卫和类型断言是处理类型不确定性的两种不同方式:

typescript
// 类型断言:告诉编译器"相信我,这就是这个类型"(不进行运行时检查)
function processWithAssertion(value: string | number): void {
  const str = value as string;  // 断言为 string
  console.log(str.toUpperCase());  // 如果 value 是 number,运行时错误
}

// 类型守卫:运行时检查,更安全
function processWithGuard(value: string | number): void {
  if (typeof value === "string") {
    // TypeScript 自动推断为 string
    console.log(value.toUpperCase());  // ✅ 安全
  } else {
    // TypeScript 自动推断为 number
    console.log(value.toFixed(2));     // ✅ 安全
  }
}

提示

优先使用类型守卫而不是类型断言。类型守卫在运行时进行实际检查,更安全可靠。类型断言应该只在确实确定类型且无法使用类型守卫时使用。

常见错误和解决方案

错误示例

typescript
// ❌ 错误:类型守卫函数没有返回类型谓词
function isStringWrong(value: unknown): boolean {
  return typeof value === "string";
  // 这样 TypeScript 不会进行类型缩小
}

function processWrong(value: unknown): void {
  if (isStringWrong(value)) {
    // TypeScript 仍然不知道 value 是 string
    console.log(value.toUpperCase());  // ❌ 类型错误
  }
}

// ❌ 错误:使用类型断言代替类型守卫
function processUnsafe(value: string | number): void {
  const str = value as string;  // 不安全的断言
  console.log(str.toUpperCase());  // 如果 value 是 number,运行时错误
}

// ❌ 错误:typeof null 返回 "object"
function processNullable(value: string | null): void {
  if (typeof value === "object") {
    // TypeScript 认为这里是 null,但 typeof null 也是 "object"
    console.log(value);  // value 是 null
  }
}

正确写法

typescript
// ✅ 正确:使用类型谓词
function isString(value: unknown): value is string {
  return typeof value === "string";
}

function process(value: unknown): void {
  if (isString(value)) {
    // TypeScript 知道这里是 string
    console.log(value.toUpperCase());  // ✅ 类型安全
  }
}

// ✅ 正确:使用类型守卫
function processSafe(value: string | number): void {
  if (typeof value === "string") {
    // TypeScript 自动推断为 string
    console.log(value.toUpperCase());  // ✅ 安全
  } else {
    // TypeScript 自动推断为 number
    console.log(value.toFixed(2));     // ✅ 安全
  }
}

// ✅ 正确:检查 null
function processNullable(value: string | null): void {
  if (value === null) {
    console.log("值为 null");
  } else {
    // TypeScript 知道这里是 string
    console.log(value.toUpperCase());  // ✅ 安全
  }
}

注意事项

提示

  • 类型守卫通过运行时检查来缩小类型范围,比类型断言更安全
  • typeof 用于检查基本类型(string、number、boolean 等)
  • instanceof 用于检查对象是否是某个类的实例
  • in 操作符用于检查对象是否包含某个属性
  • 自定义类型守卫必须返回类型谓词(parameterName is Type
  • 类型守卫可以组合使用,实现更复杂的类型检查

注意

  • typeof null 返回 "object":这是 JavaScript 的历史遗留问题。要检查 null,应该直接使用 value === nullvalue !== null
  • 类型守卫必须返回类型谓词:自定义类型守卫函数必须返回 parameterName is Type 格式,否则 TypeScript 不会进行类型缩小
  • 类型守卫是运行时检查:类型守卫在运行时进行实际检查,如果检查逻辑错误,可能导致类型推断错误
  • 避免过度使用类型断言:如果频繁使用类型断言,应该考虑使用类型守卫来替代

信息

类型守卫在以下场景特别有用:

  • 处理联合类型,需要根据运行时值来确定具体类型
  • 处理 unknown 类型,需要安全地缩小类型范围
  • 处理 API 响应,需要检查响应结构
  • 处理 DOM 元素,需要确定具体的元素类型
  • 处理 JSON 数据,需要验证数据结构
  • 处理第三方库数据,需要确保类型安全

重要

  • 类型守卫是运行时检查:类型守卫在运行时进行实际检查,确保类型安全。这与类型断言不同,类型断言只是告诉编译器如何理解类型,不进行运行时验证
  • 类型谓词是必需的:自定义类型守卫函数必须返回类型谓词(parameterName is Type),否则 TypeScript 不会进行类型缩小
  • 检查逻辑必须正确:类型守卫的检查逻辑必须准确反映类型关系,否则可能导致类型推断错误和运行时错误
  • 优先使用类型守卫:在处理类型不确定性时,优先使用类型守卫而不是类型断言,类型守卫更安全可靠

相关链接

基于 VitePress 构建