TypeScript泛型映射类型为何推断交叉类型而非联合类型?
问题场景
使用泛型映射类型TypeMap时,泛型类TestState的init方法中,为this.interfaceType赋值testObject时,TypeScript错误地将TypeMap[T]推断为InterfaceOne & InterfaceTwo(交叉类型),而非预期的InterfaceOne | InterfaceTwo(联合类型),触发类型错误。
最小复现代码:
interface InterfaceOne { fieldOne: string } interface InterfaceTwo { fieldTwo: string } enum Types { One = 'one', Two = 'two' } type TypeMap = { [Types.One]: InterfaceOne [Types.Two]: InterfaceTwo } export default class TestState<T extends Types> { private baseType: T private interfaceType: TypeMap[T] | null constructor(testFieldOne: T, testFieldTwo: TypeMap[T]) { this.baseType = testFieldOne this.interfaceType = testFieldTwo } private init(): void { if (this.baseType === Types.Two) { const testObject: InterfaceTwo = { fieldTwo: 'test' } console.log(this.interfaceType) this.interfaceType = testObject // ❌ 类型错误:Type 'InterfaceTwo' is not assignable to type 'TypeMap[T]' } } }
问题原因
TypeScript无法通过类属性this.baseType的条件检查,自动缩小泛型参数T的范围。当检查this.baseType === Types.Two时,TypeScript仅能确定this.baseType的类型是Types.Two,但无法关联到泛型T也被限定为Types.Two。此时TypeMap[T]仍被视为依赖于整个T约束(Types.One | Types.Two)的泛型类型,TypeScript会要求赋值的类型满足TypeMap[T]的所有可能情况,即推断为InterfaceOne & InterfaceTwo交叉类型,导致InterfaceTwo无法匹配。
解决方案
方案1:类型断言匹配泛型类型
在赋值时,将testObject断言为当前泛型对应的TypeMap[T],明确告诉TypeScript该值符合泛型类型要求:
private init(): void { if (this.baseType === Types.Two) { const testObject: InterfaceTwo = { fieldTwo: 'test' } this.interfaceType = testObject as TypeMap[T] } }
方案2:自定义类型守卫缩小类实例类型
定义类型守卫函数,将类实例的类型缩小为TestState<Types.Two>,让TypeScript自动推断interfaceType的类型为InterfaceTwo:
private isTypeTwo(): this is TestState<Types.Two> { return this.baseType === Types.Two } private init(): void { if (this.isTypeTwo()) { const testObject: InterfaceTwo = { fieldTwo: 'test' } this.interfaceType = testObject // ✅ 类型推断正确 } }
方案3:重构为判别式联合类型(推荐场景)
若无需保留泛型类结构,可将状态重构为判别式联合类型,利用TypeScript的自动类型缩小特性:
type TestState = | { baseType: Types.One; interfaceType: InterfaceOne | null } | { baseType: Types.Two; interfaceType: InterfaceTwo | null } function createTestState<T extends Types>(type: T, data: TypeMap[T]): TestState { return { baseType: type, interfaceType: data } } function init(state: TestState): void { if (state.baseType === Types.Two) { const testObject: InterfaceTwo = { fieldTwo: 'test' } state.interfaceType = testObject // ✅ 类型推断正确 } }
内容的提问来源于stack exchange,提问作者d1r0l

