TypeScript 联合类型、交叉类型与类型保护实战解析

Theo
Theo
发布时间:2026-10-16 | 3人浏览过
原创

TypeScript 通过联合类型和交叉类型灵活扩展了类型系统,但在处理多种可能类型的变量时,往往需要借助类型保护来收窄类型范围并安全访问属性。本文将结合代码实例,详解联合与交叉类型的定义差异,并重点剖析在混合数组中提取数据的场景下,如何利用自定义守卫函数、typeof 及 instanceof 实现有效的类型保护。

联合类型与交叉类型基础

TypeScript 提供了两种组合多个类型的方式:联合类型与交叉类型。理解它们的区别是编写健壮类型代码的前提。

第1步:定义并使用联合类型

目的:允许变量取多个类型中的任意一种。

操作:使用管道符 | 连接多个类型标识。

// 通过 | 符号定义联合类型
let value: number | boolean | string = '一碗周'
value = 18

// 上述代码定义了 value,它既可以赋值为字符串'一碗周',也可以赋值为数字 18。

第2步:定义并使用交叉类型

目的:创建一个新类型,该类型必须满足所连接的所有接口的条件(类似于逻辑“与”)。

操作:使用与符号 & 连接多个接口或类型。

// 定义三个普通接口类型
interface Name {
  name: string
}
interface Age {
  age: number
}
interface Hobby {
  hobby: string
}

// 定义一个对象,该对象必须同时拥有 name, age, hobby 属性
let person: Name & Age & Hobby = {
  name: '一碗周',
  age: 18,
  hobby: 'coding',
}

// 如果少分配一个类型,编译器将抛出异常。

场景挑战:从混合数组获取数据

考虑一个实际需求:从一个包含 number 或 string 的数组中,找到第一个数字值并返回。number 是合法的数值(包括 0),因此不能简单地用布尔值判断是否成功取值。

// 定义一个包含 number 或者 string 的数组
const arr: (number | string)[] = [1, '数字']

// 尝试直接遍历并返回
const getValue: (arr: (number | string)[]) => number = (
  arr: (number | string)[]
): number => {
  for (let i = 0; i < arr.length; i++) {
    // 仅靠 isNaN 无法让编译器确认当前值一定是 number 类型
    if (!isNaN(Number(arr[i]))) {
      return arr[i] // 报错:不能将类型“string | number”分配给类型“number”
    }
  }
}

在上述代码中,虽然逻辑上已排除非数字情况,但 TypeScript 编译器仍认为 arr[i] 可能是 string,导致类型不匹配错误。我们需要引入类型保护来解决此问题。

类型保护的三种实现方式

第3步:使用类型断言(快速但不完全安全)

可以使用 as 关键字告诉编译器,你确信该值在经过检查后属于特定类型。

const getValue: (arr: (number | string)[]) => number = (
  arr: (number | string)[]
): number => {
  for (let i = 0; i < arr.length; i++) {
    if (!isNaN(Number(arr[i]))) {
      return arr[i] as number // 强制断言为 number
    }
  }
}

缺点:类型断言是主观的。如果逻辑判断有误(例如未来代码重构导致非数字输入未被拦截),编译器不会发出警告,可能导致运行时错误。

第4步:自定义类型保护函数(推荐用于复杂逻辑)

通过返回一个类型为谓词(Type Predicate)的函数,可以构建安全的类型收窄路径。语法为 parameterName is Type。

// 1. 定义类型谓词函数
function isNumber(value: number | string): value is number {
  // 如果转换后的结果不是 NaN,则视为数字
  return !isNaN(Number(value))
}

// 2. 使用守卫函数进行收窄
const getNumberSafe: (value: number | string) => number | undefined = (
  value: number | string
): number | undefined => {
  if (isNumber(value)) {
    // 在这里 TypeScript 自动推断 value 为 number 类型
    return value 
  }
}

// 3. 在主流程中使用
const arr: (number | string)[] = [1, 'string']
for (let i = 0; i < arr.length; i++) {
  const val = getNumberSafe(arr[i])
  if (val !== undefined || val === 0) { // 0 也是有效数字
    console.log(val)
    break
  }
}

优势:编译器能跟踪 isNumber 函数的执行结果,在进入 if 分支后自动将 value 类型缩小为 number,既安全又语义清晰。

第5步:使用 typeof 进行基本类型保护

typeof 操作符可直接用于原始类型区分。注意:typeof 仅支持 "string", "number", "boolean", "symbol" 等几种原始类型字符串。

const getPrimitiveNumber = (value: number | string): number | undefined => {
  // 直接检查值的类型字符串是否为 "number"
  if (typeof value === 'number') {
    return value
  }
}

第6步:使用 instanceof 进行引用类型保护

instanceof 用于检测构造函数的原型链,适用于类(Class)或包装对象(Wrapper Objects)。由于原始值(如字面量 1)没有原型链特性,若要使用 instanceof,需显式使用对象类型 Number 或 String。

// 注意:这里使用 Number 和 String 对象而非原始类型
const arrObj: (Number | String)[] = [new Number(10), new String('text')]

const getObjectNumber = (value: Number | String): number => {
  if (value instanceof Number) {
    // 窄化为 Number 对象,需调用 .valueOf() 或直接使用 Number(value)
    return Number(value)
  }
  throw new Error('Not a number')
}

注意事项: 1. instanceof 只适用于引用类型(对象/类),不适用于基本包装类型(如 number vs Number)的字面量场景。 2. 涉及继承时,需确保父类原型链中包含子类的原型对象,否则判断会失效。

总结

在处理 TypeScript 联合类型带来的类型不确定性时,应避免过度依赖风险较高的类型断言。对于简单的基础类型判断,typeof 最为直观;对于对象实例的判断,instanceof 是标准做法;而对于复杂的业务逻辑判断(如混合了字面量和对象的数组),推荐使用自定义类型谓词函数来实现安全、可维护的类型收窄。

以上就是 TypeScript 联合类型、交叉类型与类型保护的详细内容,更多关于 TypeScript 高级类型应用的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表