如何在TypeScript泛型函数中正确处理联合类型?
问题背景
先看这段示例代码:
type AB = { foo: "A" bar: "A" } | { foo: "B" bar: "B" } type Foo = AB["foo"] function fun<F extends Foo>(foo: F, bar: Extract<AB, { foo: F }>["bar"]) { fun2({ foo, bar, }) } function fun2(ab: AB) { }
运行时会抛出错误:
类型为'{ foo: "A" | "B"; bar: "A" | "B"; }'的参数无法赋值给类型'AB'的参数。
接下来针对两个核心问题给出解决方案:
问题1:如何限制泛型类型不接受联合类型?
可以利用TypeScript联合类型的分配特性,构造条件类型约束来阻止泛型接收联合类型:
type NoUnion<T> = [T] extends [UnionToIntersection<T>] ? T : never;
核心逻辑:如果T是联合类型,UnionToIntersection<T>会将其转为交叉类型,此时[T]与[交叉类型]不匹配,返回never;如果T是单个类型,两者匹配,返回T。
修改后的泛型函数:
function fun<F extends NoUnion<Foo>>(foo: F, bar: Extract<AB, { foo: F }>["bar"]) { fun2({ foo, bar }); }
这样当传入联合类型作为F时,TypeScript会直接报错,确保泛型仅接收单个类型,无需枚举所有类型或使用不推荐的联合转元组方案。
问题2:如何避免bar: Extract<AB, { foo: F }>["bar"]返回联合类型?
核心原因
当泛型F是联合类型时,Extract<AB, { foo: F }>会因分配性遍历联合成员,最终返回AB本身,导致bar的类型变为"A" | "B";而直接写Extract<AB, { foo: "A" }>时,foo是具体字面量,自然返回对应单类型的bar。
另外,指定fun<F extends "A">时,TypeScript会保留泛型参数F的标识(即便F实际等于"A"),不会自动将其收窄为字面量"A",导致Extract操作无法精准匹配,最终返回联合类型。
解决方案
通过泛型条件绑定,让bar类型与foo严格关联:
// 方式一:显式分支判断 function fun<F extends Foo>(foo: F, bar: F extends "A" ? "A" : "B") { fun2({ foo, bar }); } // 方式二:通用类型提取 type BarForFoo<F extends Foo> = AB extends { foo: F; bar: infer B } ? B : never; function fun<F extends Foo>(foo: F, bar: BarForFoo<F>) { fun2({ foo, bar }); }
两种方式都能确保bar的类型与foo严格一致,不会返回联合类型。
针对附带疑问:指定fun<F extends "A">时显示foo: F而非foo: "A",是因为TypeScript会保留泛型参数的标识,除非使用as const或显式断言,否则不会自动将F替换为具体字面量"A"。
内容的提问来源于stack exchange,提问作者TN.

