TypeScript如何通过单个泛型参数从对象反向推导类型?
单个泛型参数实现从对象键推断转换后类型的方案
问题背景
你希望创建一个仅接收单个泛型参数<T extends Type>的函数,能从传入的params对象的键(如customerId)自动推断出T为对应的原始类型(如'customer'),但现有代码中T总是被推断为整个Type联合类型,无法自动缩小到单个成员。
你的初始实现代码:
type Type = 'customer' | 'supplier' type EncodedType<T extends Type> = `${T}Id`; // 生成单键对象的联合类型 type Params<T extends Type> = { [P in T]: Record<EncodedType<P>, string>; }[T]; function foo<T extends Type>(params: Params<T>) { } // 调用时T被推断为Type,而非'customer' foo({customerId: '42'})
问题原因
你定义的Params<T>是分发后的联合类型:当T为Type(即'customer' | 'supplier')时,Params<T>等价于{customerId: string} | {supplierId: string}。传入单个键的对象时,TypeScript认为该对象属于这个联合类型,因此会默认将T推断为整个Type联合,而不会自动缩小到单个成员。
解决方案
调整参数类型定义,让T为联合类型时对应的参数类型必须包含所有编码后的键,这样传入单个键的对象只能匹配T为单个成员的情况,从而触发正确的泛型推断。
基础实现(允许同时传入多键)
type Type = 'customer' | 'supplier'; type EncodedType<T extends Type> = `${T}Id`; // 直接用Record<EncodedType<T>, string>作为参数类型 function foo<T extends Type>(params: Record<EncodedType<T>, string>) { // 函数逻辑 } // 调用测试 foo({ customerId: '42' }); // T自动推断为'customer' foo({ supplierId: '123' }); // T自动推断为'supplier' // 传入多键时,T会被推断为'customer' | 'supplier' foo({ customerId: '42', supplierId: '123' }); // 符合类型要求
严格实现(禁止同时传入多键)
如果需要限制参数只能有单个键,可以结合映射类型和交叉类型实现:
type Type = 'customer' | 'supplier'; type EncodedType<T extends Type> = `${T}Id`; // 生成仅包含单个编码键的对象类型联合 type SingleKeyParams = { [T in Type]: Record<EncodedType<T>, string>; }[Type]; // 通过交叉类型约束,确保参数只能匹配单个T对应的类型 function foo<T extends Type>( params: SingleKeyParams & Record<EncodedType<T>, string> ) {} foo({ customerId: '42' }); // T推断为'customer' foo({ supplierId: '123' }); // T推断为'supplier' // 传入多键会触发类型错误 foo({ customerId: '42', supplierId: '123' }); // 类型不匹配
补充示例的修复
针对你提供的补充示例,用同样逻辑修复后,T能自动推断为单个成员:
type Type = 'foo' | 'bar'; // 定义每个Type成员对应的参数类型 type TypeParams = { foo: {a: number}; bar: {b: number}; }; // 调整函数签名,让TypeScript反向推断T function func<T extends Type>(params: TypeParams[T]): Uppercase<T>; // 函数实现 function func(params: TypeParams[keyof TypeParams]) { return params.a ? 'FOO' : 'BAR' as any; } const result = func({a: 42}); // result类型为'FOO',T推断为'foo' const result2 = func({b: 123}); // result2类型为'BAR',T推断为'bar'
核心逻辑
TypeScript的泛型推断倾向于匹配最宽泛的类型,当参数类型是联合类型时,它不会自动缩小泛型参数到单个成员。要实现从对象键推断T,需要让泛型参数为联合类型时对应的参数类型与传入的单个键对象不兼容,从而迫使TypeScript选择能匹配的单个泛型成员。
内容的提问来源于stack exchange,提问作者JHH
相关产品推荐
相关产品推荐

