TypeScript 联合类型、交叉类型与类型保护实战解析
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 高级类型应用的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









