泛型无法实现类型收窄:如何限制参数为同一具体类型
问题:实现参数必须为同一类型的equal函数
我通过类型约束数据的可能表示,涉及的值类型为Foo或Bar(均为string的别名),即所有值属于Foo | Bar类型。希望编写一个equal函数,要求两个参数必须为同一类型,若传入不同类型参数则编译失败。
尝试了以下代码,但它能正常编译,不符合预期:
type Foo = string; type Bar = string; type FooBar = Foo | Bar; function equal<T extends FooBar>(n1: T, n2: T): boolean { return n1 === n2; } const n1: Foo = "foo"; const n2: Bar = "bar"; equal(n1, n2); // 期望编译失败,但实际通过
推测是类型拓宽导致编译器将两者视为FooBar宽类型,请问如何指定泛型T只能是Foo或Bar,而非FooBar?
原因分析
TypeScript采用结构类型系统,Foo和Bar都是string的别名,本质属于同一类型,编译器无法从语义上区分它们。泛型T会被自动推断为Foo | Bar(即string),自然不会触发编译错误。
要实现需求,必须让Foo和Bar成为语义独立的类型,可通过以下两种方式实现:
方案1:使用字面量类型(适用于固定值场景)
如果Foo和Bar对应固定的字符串字面量,直接定义为字面量类型即可:
type Foo = "foo"; type Bar = "bar"; function equal<T extends Foo | Bar>(n1: T, n2: T): boolean { return n1 === n2; } const n1: Foo = "foo"; const n2: Bar = "bar"; equal(n1, n2); // 编译失败:类型"bar"的参数不能赋给类型"foo"的参数 equal(n1, n1); // 编译通过 equal(n2, n2); // 编译通过
方案2:使用品牌类型(Branded Types,适用于任意字符串场景)
如果Foo和Bar是任意字符串,但需要语义区分,可以给类型添加唯一的品牌标记:
type Foo = string & { __brand: "Foo" }; type Bar = string & { __brand: "Bar" }; // 辅助函数用于创建带品牌的类型实例 function createFoo(value: string): Foo { return value as Foo; } function createBar(value: string): Bar { return value as Bar; } function equal<T extends Foo | Bar>(n1: T, n2: T): boolean { return n1 === n2; } const n1 = createFoo("foo"); const n2 = createBar("bar"); equal(n1, n2); // 编译失败:类型"Bar"的参数不能赋给类型"Foo"的参数 equal(n1, createFoo("anotherFoo")); // 编译通过 equal(n2, createBar("anotherBar")); // 编译通过
品牌类型通过交叉类型给string添加一个虚构的唯一属性(__brand),让TypeScript认为Foo和Bar是不同类型,但运行时该属性不存在,不会影响代码执行逻辑。
内容的提问来源于stack exchange,提问作者Pamplemousse
相关产品推荐
相关产品推荐

