返回 导航

其他

hangge.com

TypeScript - 类型缩小详解(typeof、instanceof、in、平等缩小、自定义类型保护)

作者:hangge | 2026-08-07 08:55
        在 TypeScript 中,缩小类型的过程被称为类型缩小(Type Narrowing)。当我们使用条件语句检查值的类型时,TypeScript 能够在特定的执行路径中缩小变量的类型,使其比声明时更为精确。本文将详细介绍 TypeScript 中常用的类型缩小方式,包括 typeofinstanceofin 操作符、平等缩小以及自定义类型保护等内容。

一、基本介绍

1,什么是类型缩小

(1)类型缩小Type Narrowing)是 TypeScript 中的一个重要概念。例如,我们可以使用类似 typeof padding === "number" 的条件语句改变 TypeScript 的执行路径,在特定的执行路径中缩小变量的类型。
提示:我们编写的 typeof padding === "number" 这种缩小类型的代码就是一种类型保护Type Guard)代码。类型保护可以帮助我们在编译时获得更精确的类型信息。

(2)TypeScript 中常见的类型保护方式:
  • typeof:检查值的类型,如 typeof x === "number"
  • instanceof:检查值是否为某个类的实例,如 d instanceof Date
  • in:检查对象是否具有某个属性,如 "name" in obj
  • 平等缩小:使用 ===!==switch 等进行类型缩小
  • 自定义类型保护:使用类型谓词 is 定义类型保护函数

二、typeof 类型保护

1,基本用法

(1)JavaScript 支持 typeof 运算符,可以使用该运算符来检查值的类型。在 TypeScript 中,可以通过检查值的类型来缩小不同分支中的类型。

(2)下面是一个使用 typeof 实现类型缩小的示例。可以看到,这里使用 typeof 检查 id 的类型。如果 id 的类型是字符串,则调用 toUpperCase 方法。通过使用 typeof 进行类型判断,我们将联合类型的 id 缩小为更具体的字符串类型。
type IDType = number | string;

function printID(id: IDType) {
  // 使用 typeof 实现类型缩小,将 id 从联合类型缩小为 string 类型
  if (typeof id === 'string') {
    // 在这个分支中,TypeScript 知道 id 是 string 类型
    console.log(id.toUpperCase());
  } else {
    // 在这个分支中,TypeScript 知道 id 是 number 类型
    console.log(id);
  }
}

printID("hello");  // 输出: HELLO
printID(123);      // 输出: 123

2,typeof 支持的类型

typeof 类型保护只有特定的形式能被 TypeScript 识别:
注意typeof null 返回 "object",这是 JavaScript 的历史遗留问题。如果需要检查 null,应该使用 x === null 而不是 typeof
// TypeScript 能识别的 typeof 类型保护形式
if (typeof x === "number") { /* x 是 number 类型 */ }
if (typeof x === "string") { /* x 是 string 类型 */ }
if (typeof x === "boolean") { /* x 是 boolean 类型 */ }
if (typeof x === "symbol") { /* x 是 symbol 类型 */ }
if (typeof x === "undefined") { /* x 是 undefined 类型 */ }
if (typeof x === "function") { /* x 是 Function 类型 */ }
if (typeof x === "object") { /* x 是 object 类型(注意:null 也是 object) */ }

三、平等缩小

1,基本概念

(1)平等缩小Equality Narrowing)是指使用 switch===!====!= 等运算符来表达相等性,进而实现类型缩小。

(2)下面是一个使用平等缩小的示例:
type Direction = "left" | "right" | "top" | "bottom";

function printDirection(direction: Direction) {
  // 使用 if 判断,缩小类型
  if (direction === 'left') {
    // 在这个分支中,direction 被缩小为 "left" 字面量类型
    console.log("向左移动");
  } else if (direction === 'right') {
    console.log("向右移动");
  } else {
    // 在这个分支中,direction 只能是 "top" 或 "bottom"
    console.log("垂直移动");
  }
}

printDirection("left");  // 输出: 向左移动

2,switch 语句缩小

switch 语句也是实现平等缩小的常用方式,特别适合处理多个分支的情况:
提示:使用 switch 语句进行类型缩小特别适合处理字面量联合类型,可以让代码更加清晰易读。
type Direction = "left" | "right" | "top" | "bottom";

function printDirection(direction: Direction) {
  // 使用 switch 判断,缩小类型
  switch (direction) {
    case 'left':
      console.log("向左移动");
      break;
    case 'right':
      console.log("向右移动");
      break;
    case 'top':
      console.log("向上移动");
      break;
    case 'bottom':
      console.log("向下移动");
      break;
  }
}

printDirection("top");  // 输出: 向上移动

四、instanceof 类型保护

1,基本用法

(1)JavaScript 中有一个 instanceof 运算符,用于检查一个值是否为另一个值的实例。TypeScript 会自动缩小由 instanceof 保护的分支中的类型。
提示instanceof 只能用于类(class)类型的检查,不能用于接口(interface)类型,因为接口在编译后会被移除。

(2)下面演示 instanceof 运算符的使用。可以看到,在 work 函数中通过判断变量 p 是否为 Student 类型的实例,将联合类型的 p 缩小为更具体的 Student 类型。
class Student {
  studying() {
    console.log("正在学习");
  }
}

class Teacher {
  teaching() {
    console.log("正在教学");
  }
}

function work(p: Student | Teacher) {
  // 判断 p 是否为 Student 类型的实例,进行类型缩小
  if (p instanceof Student) {
    // 在这个分支中,p 被缩小为 Student 类型
    p.studying();
  } else {
    // 在这个分支中,p 被缩小为 Teacher 类型
    p.teaching();
  }
}

const stu = new Student();
work(stu);  // 输出: 正在学习

五、in 操作符类型保护

1,基本用法

(1)JavaScript 中有一个 in 运算符,用于判断对象是否具有指定名称的属性。如果指定的属性存在于该对象或其原型链中,那么 in 运算符将返回 true

(2)TypeScript 中可以使用 in 操作符来缩小对象类型。通过下面样例可以看到,在 walk 函数中通过判断 swimming 是否为 animal 对象中的属性,将 animal 的联合类型缩小为更具体的 Fish 类型。
// 定义 Fish 和 Dog 为对象类型
type Fish = {
  swimming: () => void;  // swimming 是函数类型
};

type Dog = {
  running: () => void;   // running 是函数类型
};

function walk(animal: Fish | Dog) {
  // 判断 swimming 是否为 animal 对象中的属性,进行类型缩小
  if ('swimming' in animal) {
    // 在这个分支中,animal 被缩小为 Fish 类型
    animal.swimming();
  } else {
    // 在这个分支中,animal 被缩小为 Dog 类型
    animal.running();
  }
}

// 创建 fish 对象,该对象的类型为 Fish 类型
const fish: Fish = {
  swimming() {
    console.log("swimming");
  }
};

walk(fish);  // 输出: swimming

六、自定义类型保护

1,类型谓词 is

(1)有时候我们需要更复杂的类型检查逻辑,这时可以定义自定义类型保护。自定义类型保护使用类型谓词Type Predicate)来指定返回值的类型。

(2)类型谓词的语法格式为:parameterName is Type,其中 parameterName 必须是当前函数签名里的一个参数名。下面是一个简单的样例:
提示:每当使用变量调用类型保护函数时,TypeScript 会将该变量缩小为类型谓词指定的具体类型。
type Fish = {
  swimming: () => void;
};

type Bird = {
  flying: () => void;
};

// 定义类型保护函数,返回类型是类型谓词
function isFish(pet: Fish | Bird): pet is Fish {
  return 'swimming' in pet;
}

function move(pet: Fish | Bird) {
  if (isFish(pet)) {
    // TypeScript 知道在这个分支中 pet 是 Fish 类型
    pet.swimming();
  } else {
    // TypeScript 知道在这个分支中 pet 是 Bird 类型
    pet.flying();
  }
}

const fish: Fish = {
  swimming() {
    console.log("鱼在游泳");
  }
};

move(fish);  // 输出: 鱼在游泳

2,类型保护函数的优势

(1)使用自定义类型保护的优势:
  • 代码复用:可以将复杂的类型检查逻辑封装成函数,在多处使用
  • 类型安全TypeScript 会自动根据返回值缩小类型
  • 可读性:函数名可以清晰地表达类型检查的意图

(2)下面是一个更复杂的自定义类型保护示例:
interface Admin {
  name: string;
  privileges: string[];  // 管理员特有的权限列表
}

interface User {
  name: string;
  startDate: Date;      // 普通用户特有的注册日期
}

// 自定义类型保护函数
function isAdmin(account: Admin | User): account is Admin {
  return 'privileges' in account;
}

function printAccountInfo(account: Admin | User) {
  console.log("用户名:", account.name);
  
  if (isAdmin(account)) {
    console.log("权限列表:", account.privileges);
  } else {
    console.log("注册日期:", account.startDate);
  }
}

const admin: Admin = {
  name: "管理员",
  privileges: ["read", "write", "delete"]
};

printAccountInfo(admin);
// 输出:
// 用户名: 管理员
// 权限列表: ["read", "write", "delete"]
评论

全部评论(0)

回到顶部