TypeScript无法通过参数类型推断函数泛型类型的问题求助
问题场景
函数签名:
public async createSubscriberChannel< T extends Messages.PubsubParams, K extends Messages.PubsubMessage<T> >(metadata: ChannelMetadata<T, K, TbNextgenQueue>, args: SubscriberArgs<T>)
调用代码:
const publisherChannel = await rabbitmqController.createPublisherChannel( new TaskCreatedMetadata(), { exchange: TbNextgenExchange.TASK_CREATED, exchangeOptions: { durable: false, }, } );
TaskCreatedMetadata定义:
export class TaskCreatedMetadata extends ChannelMetadata< Messages.TaskCreatedParams, Messages.PubsubMessage<Messages.TaskCreatedParams>, TbNextgenQueue.TASK_CREATED > { readonly queueName = TbNextgenQueue.TASK_CREATED; }
当前问题:调用createSubscriberChannel时,TypeScript没法自动推断泛型参数T为Messages.TaskCreatedParams,反而识别成了父类型Messages.PubsubParams。
核心原因
当实例化TaskCreatedMetadata后,TypeScript默认会把它当成ChannelMetadata<Messages.PubsubParams, Messages.PubsubMessage<Messages.PubsubParams>, TbNextgenQueue>的子类——泛型的协变特性导致父类的约束覆盖了子类的具体类型,函数无法从metadata参数中提取出精准的T类型。
可行解决方案
方案1:用条件类型反向推断泛型
直接重构函数的泛型定义,让TS从传入的metadata类型里反向推导T和K:
public async createSubscriberChannel< M extends ChannelMetadata<any, any, TbNextgenQueue>, T extends Messages.PubsubParams = M extends ChannelMetadata<infer U, any, any> ? U : never, K extends Messages.PubsubMessage<T> = M extends ChannelMetadata<any, infer V, any> ? V : never >(metadata: M, args: SubscriberArgs<T>)
这里用infer关键字从metadata的类型M里提取出具体的U(也就是我们要的T)和V(对应K),传入TaskCreatedMetadata实例时,TS会自动识别出T是Messages.TaskCreatedParams。
方案2:给Metadata类显式暴露类型标记
在TaskCreatedMetadata里加一个仅用于类型推断的属性:
export class TaskCreatedMetadata extends ChannelMetadata< Messages.TaskCreatedParams, Messages.PubsubMessage<Messages.TaskCreatedParams>, TbNextgenQueue.TASK_CREATED > { readonly queueName = TbNextgenQueue.TASK_CREATED; // 仅用于类型推断,不需要赋值 declare readonly __typeT: Messages.TaskCreatedParams; }
然后调整函数的泛型约束,让它能识别这个标记:
public async createSubscriberChannel< T extends Messages.PubsubParams, K extends Messages.PubsubMessage<T>, M extends ChannelMetadata<T, K, TbNextgenQueue> & { __typeT: T } >(metadata: M, args: SubscriberArgs<T>)
这样TS就能通过__typeT属性精准定位到T的具体类型。
方案3:用静态属性统一标记类型
如果有多个类似的Metadata子类,可以给它们统一加静态类型属性:
export class TaskCreatedMetadata extends ChannelMetadata< Messages.TaskCreatedParams, Messages.PubsubMessage<Messages.TaskCreatedParams>, TbNextgenQueue.TASK_CREATED > { readonly queueName = TbNextgenQueue.TASK_CREATED; static readonly typeT: Messages.TaskCreatedParams; }
然后修改函数泛型,通过构造函数的静态属性推断:
public async createSubscriberChannel< M extends typeof TaskCreatedMetadata, T extends Messages.PubsubParams = M['typeT'] >(metadata: InstanceType<M>, args: SubscriberArgs<T>)
这个方案适合有统一规范的场景,所有Metadata子类都要遵循这个静态属性约定。
内容的提问来源于stack exchange,提问作者Matias Lareo

