TypeScript泛型函数逆变问题:TComp按类型名分发报错如何解决
报错原因
- TypeScript 泛型存在收窄限制:你对
name做的分支判断,只会收窄name本身的联合类型,不会联动收窄泛型T的实际类型,也无法推导此时cb、value的类型和name是绑定匹配的。 - 函数参数属于逆变位置,类型校验规则更严格:
f1要求传入的cb是接收number类型入参的函数,而此时 TypeScript 视角下cb的入参类型还是未收窄的TNameTypeMap[T](可能是string | number),逆变校验下要求f1的cb入参number可以赋值给TNameTypeMap[T],但 TypeScript 无法证明这一点,最终推导到never类型,抛出类型不兼容错误。
修复方案
方案1:类型断言(适合小型场景快速修复)
确定分支内name、cb、value的类型匹配的前提下,直接用as做类型断言即可解决报错:
const sf = <T extends TStrNum>(cb: (a: TNameTypeMap[T]) => void, value: TNameTypeMap[T], name: T) => { if(name==='num') { return f1(cb as (a: number) => void, value as number); } if(name==='str') { return f2(cb as (a: string) => void, value as string); } }
方案2:函数重载(兼顾对外类型安全和内部实现灵活性)
对外暴露严格的泛型重载签名保证调用时的类型安全,内部实现用宽松的联合类型规避泛型收窄问题:
// 对外重载签名,保证调用时类型校验严格 function sf(cb: (a: number) => void, value: number, name: 'num'): void function sf(cb: (a: string) => void, value: string, name: 'str'): void // 内部实现签名,用联合类型降低校验要求 function sf(cb: ((a: number) => void) | ((a: string) => void), value: number | string, name: TStrNum) { if(name==='num') { return f1(cb as (a: number) => void, value as number); } if(name==='str') { return f2(cb as (a: string) => void, value as string); } }
方案3:运行时映射表(最推荐,天然类型安全)
把TComp的实现和类型映射绑定,消除分支判断,不需要任何断言即可通过类型校验,后续拓展也更方便:
// 构造类型和实现的映射表 const compMap: {[K in TStrNum]: TComp<TNameTypeMap[K]>} = { num: f1, str: f2 } const sf = <T extends TStrNum>(cb: (a: TNameTypeMap[T]) => void, value: TNameTypeMap[T], name: T) => { return compMap[name](cb, value); }
内容的提问来源于stack exchange,提问作者Nyavro
相关产品推荐
相关产品推荐

