Typescript中的泛型函数映射

时间:2019-01-21 19:29:30

标签: typescript

我有一个Message接口,该接口带有一个通用参数T并将T的值设置为名为body的内部属性的类型: / p>

interface Message<T> {
  body: T
}

我还有一个EventHandler接口,该接口描述了一个以Message作为唯一参数的泛型函数:

interface EventHandler<T> {
  (event: Message<T>): void
}

最后,我有一个Integration类,该类具有一个subscribe方法和一个subscriptions映射,两者看起来都如下所示:

class Integration {
  public subscriptions: new Map<string, EventHandler>()
  protected subscribe<T>(eventName: string, handler: EventHandler<T>) {
    this.subscriptions.set(eventName, handler)
  }
}

最终,我希望用户能够像这样定义自己的Integration实例:

interface MyEventProperties {
  foo: string
}

class MyIntegration extends Integration {
   constructor() {
     super()
     this.subscribe('My Event Name', this.myEventHandler)
   }
   myEventHandler(event: Message<MyEventProperties>) {
     // do something
   }
}

问题是这不起作用。因为我不知道所有EventHandler函数都会得到的泛型类型,所以在实例化Map时无法定义第二个泛型参数:

class Integration {
  public subscriptions: new Map<string, EventHandler>()
  // This errors with: Generic type 'EventHandler<T>' requires 1 type argument(s).

  protected subscribe<T>(eventName: string, handler: EventHandler<T>) {
    this.subscriptions.set(eventName, handler)
  }
}

我的问题是,我是错误地解决了这个问题,还是缺少更明显的东西?

1 个答案:

答案 0 :(得分:2)

简单的解决方案是使用unknown作为映射中的通用参数。以下解决方案将起作用:

interface Message<T> {
    body: T
}
interface EventHandler<T> {
    (event: Message<T>): void
}

class Integration {
    public subscriptions = new Map<string, EventHandler<unknown>>()
    protected subscribe<T>(eventName: string, handler: EventHandler<T>) {
        this.subscriptions.set(eventName, handler)
    }
    public publishEvent<T>(eventName: string, msg: Message<T>) {
        (this.subscriptions.get(eventName) as EventHandler<T>)(msg);
    }
}

interface MyEventProperties { foo: string }
class MyIntegration extends Integration {
    constructor() {
        super()
        this.subscribe('My Event Name', this.myEventHandler)
        this.subscribe('My Event Name Mistaken', this.myEventHandler) // no relation between name and event 
    }
    myEventHandler(event: Message<MyEventProperties>) {
        // do something
    }
}

问题是事件名称和事件类型之间没有关系。订阅(如上突出显示)时和发出事件时(可能不会根据预期的消息类型检查消息正文)都可能是一个问题

我建议在事件类型和名称之间添加一个映射类型:

interface Message<T> {
    body: T
}
interface EventHandler<T> {
    (event: Message<T>): void
}

class Integration<TEvents> {
    public subscriptions = new Map<PropertyKey, EventHandler<unknown>>()
    protected subscribe<K extends keyof TEvents>(eventName: K, handler: EventHandler<TEvents[K]>) {
        this.subscriptions.set(eventName, handler)
    }
    public publishEvent<K extends keyof TEvents>(eventName: K, msg: Message<TEvents[K]>) {
        (this.subscriptions.get(eventName) as EventHandler<TEvents[K]>)(msg);
    }
}

interface MyEventProperties { foo: string }

class MyIntegration extends Integration<{
    'My Event Name': MyEventProperties
}> {
    constructor() {
        super()
        this.subscribe('My Event Name', this.myEventHandler)
        this.subscribe('My Event Name Mistaken', this.myEventHandler) // error now
        this.subscribe('My Event Name', (e: Message<string>) => { }) // error, type of body not ok  
    }
    myEventHandler(event: Message<MyEventProperties>) {
        // do something
    }
}

let m = new MyIntegration();
m.publishEvent("My Event Name", 0) // error body not ok 
m.publishEvent("My Event Name M") // error mistaken name
m.publishEvent("My Event Name", {
    body: { foo: ""}
}) // ok message and body correct