如何阻止TypeScript中交叉类型绕过联合类型限制?
TypeScript交叉类型匹配问题的解决方法
问题场景
以下代码中,函数fn的参数类型定义为number | { a: number },但交叉类型(如number & { a: string }或string & { a: number })也能成功传入,导致不符合预期的类型通过了类型检查:
function fn(_: number | { a: number }) {} declare const x: number & { a: string }; // 此处a的类型为string而非number fn(x); // 无错误 declare const y: string & { a: number }; // 此处基础类型为string而非number fn(y); // 无错误
这类交叉类型可通过Object.assign(1, { a: 9 })生成,而函数设计目的是区分纯number(用作索引)和拥有a: number的对象(执行其他操作),因此需要限制交叉类型的传入。
解决方案
1. 使用重载函数明确参数类型
TypeScript支持函数重载签名,通过定义纯number和纯对象的重载,可以直接拒绝交叉类型的匹配:
// 重载签名:仅接受纯number或纯{ a: number }类型 function fn(_: number): void; function fn(_: { a: number }): void; // 实现签名:兼容两种类型 function fn(_: number | { a: number }) { if (typeof _ === 'number') { // 处理索引逻辑 } else { // 处理对象逻辑 } } declare const x: number & { a: string }; fn(x); // 类型错误:交叉类型不匹配任何重载签名 declare const y: string & { a: number }; fn(y); // 类型错误:交叉类型不匹配任何重载签名
2. 用条件类型排除交叉类型
自定义条件类型,过滤掉同时属于number和对象的交叉类型:
// 仅保留纯number(排除同时是对象的交叉类型) type PureNumber<T> = T extends number ? (object extends T ? never : T) : never; // 仅保留纯{ a: number }对象(排除同时是number的交叉类型) type PureObj<T> = T extends { a: number } ? (number extends T ? never : T) : never; // 有效参数类型:纯number 或 纯{ a: number }对象 type ValidParam = PureNumber<number> | PureObj<{ a: number }>; function fn(_: ValidParam) {} declare const x: number & { a: string }; fn(x); // 类型错误:x是交叉类型,不属于ValidParam declare const y: string & { a: number }; fn(y); // 类型错误:y是交叉类型,不属于ValidParam
3. 运行时补充类型校验
即使通过了编译时类型检查,也可以在运行时添加逻辑,确保参数符合预期:
function fn(_: number | { a: number }) { if (typeof _ === 'number') { console.log('使用number作为索引:', _); } else if (typeof _.a === 'number') { console.log('处理记录对象:', _.a); } else { // 对不符合的参数抛出错误或做降级处理 throw new Error('参数不符合要求:必须是纯number或拥有a:number的对象'); } } // 生成交叉类型实例 const crossType = Object.assign(1, { a: 'invalid' }); fn(crossType); // 运行时抛出错误
内容的提问来源于stack exchange,提问作者Miguel Leon
相关产品推荐
相关产品推荐

