如何让TypeScript对类型别名参数倒置的函数调用触发报错?
解决TypeScript中同结构类型别名无法区分的问题
你遇到的情况其实戳中了TypeScript类型系统的核心特点——它默认采用结构类型系统,而非Java、C#这类语言常用的名义类型系统。简单来说,只要两个类型的结构完全一致,TypeScript就会判定它们等价,不管你给它们起了多么不同的别名。在你的代码里,first和second都是number的别名,结构完全重合,所以TypeScript会把它们当成同一个类型处理,自然不会在参数顺序颠倒时触发报错。
要实现你期望的“按类型名义区分”的效果,我们可以通过给类型添加**唯一标记属性(Branding)**来打破结构一致性,同时不影响代码的运行性能。这里有几种实用方案:
方案1:添加只读品牌属性(最常用)
通过交叉类型给每个类型别名附加一个唯一的、只读的品牌属性,让TypeScript识别出它们是不同的类型:
// 给每个类型添加唯一的类型标记(仅存在于类型层面) type first = number & { readonly __brand: 'first' }; type second = number & { readonly __brand: 'second' }; // 编写转换函数,方便创建对应类型的值 const createFirst = (num: number): first => num as first; const createSecond = (num: number): second => num as second; // 使用示例 let f = createFirst(1); let s = createSecond(2); const func = (arg1: first, arg2: second) => { }; func(s, f); // 这里会触发报错!完全符合你的预期
这个方案的优势很明显:品牌属性是只读的,不会被意外修改;而且它仅存在于类型检查阶段,运行时不会产生额外的属性或性能开销。
方案2:用类实现名义类型
如果你可以接受运行时存在类实例,也可以通过类的私有属性来区分类型:
class First { // 私有属性会让TypeScript判定两个类结构不同 private readonly _brand: undefined; constructor(public value: number) {} } class Second { private readonly _brand: undefined; constructor(public value: number) {} } let f = new First(1); let s = new Second(2); const func = (arg1: First, arg2: Second) => { }; func(s, f); // 报错:类型不兼容
TypeScript对私有属性有特殊处理逻辑:即使两个类的公开结构完全一致,只要它们包含私有属性,就会被判定为不同类型。
方案3:用枚举作为品牌标记
你也可以用枚举定义清晰的品牌值,让类型区分更直观:
enum TypeBrand { FirstType = 'first', SecondType = 'second' } type first = number & { readonly brand: TypeBrand.FirstType }; type second = number & { readonly brand: TypeBrand.SecondType }; const createFirst = (num: number): first => num as first; const createSecond = (num: number): second => num as second; let f = createFirst(1); let s = createSecond(2); const func = (arg1: first, arg2: second) => { }; func(s, f); // 触发报错
总的来说,核心思路就是打破两个类型的结构一致性,让TypeScript能识别它们的“名义差异”而非仅仅是结构。其中方案1是最轻量化的选择,适合大多数场景。
内容的提问来源于stack exchange,提问作者Edo
相关产品推荐
相关产品推荐

