代码之家  ›  专栏  ›  技术社区  ›  Saad

在使用流扩展EventEmitter的类中限制“eventName”的类型?

  •  8
  • Saad  · 技术社区  · 6 年前

    例如,假设我有一个只发出三个可能事件的类 'pending' 'success' 'failure' 。此外,在 eventHandler 取决于发出的事件

    • 如果 '挂起' 这个 事件处理程序 未收到任何参数
    • 如果 '成功' 这个 事件处理程序 接收 number
    • 如果 '失败' 这个 事件处理程序 接收 Error

    下面是我如何尝试建模的:

    // @flow
    
    import EventEmitter from 'events'
    
    type CustomEventObj = {|
      pending: void,
      success: number,
      error: Error
    |}
    
    declare class MyEventEmitter extends EventEmitter {
      on<K: $Keys<CustomEventObj>>(
        eventName: K,
        eventHandler: (
          e: $ElementType<CustomEventObj, K>, 
          ...args: Array<any>
        ) => void
      ): this
    }
    

    但是,这会导致如下错误:

    Error ┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈┈ test.js:12:3
    
    Cannot extend EventEmitter [1] with MyEventEmitter because an indexer property is missing in CustomEventObj [2] in the
    first argument of property on.
    
     [1]  3│ import EventEmitter from 'events'
           :
          8│   error: Error
          9│ |}
         10│
         11│ declare class MyEventEmitter extends EventEmitter {
     [2] 12│   on<K: $Keys<CustomEventObj>>(
         13│     eventName: K,
         14│     eventHandler: (
         15│       e: $ElementType<CustomEventObj, K>, 
         16│       ...args: Array<any>
         17│     ) => void
         18│   ): this
         19│ }
         20│
    

    我不想在上有索引器属性 CustomEventObj 因为这难道不会扼杀只有3个可能事件的意义吗?

    任何帮助都将不胜感激。

    1 回复  |  直到 6 年前
        1
  •  3
  •   James Kraus    6 年前

    嗯,我有好消息和坏消息。好消息是,您将能够获得Flow来理解作为参数传递给 on 处理程序。坏消息是,您将无法告诉Flow不允许您想要的事件之外的其他事件。

    因为您在扩展EventEmitter类,所以您明确声明您的类将执行与EventEmitter相同的操作。因此,因为EventEmitter 在…上 处理程序将接收任何 string ,您的“MyEventMitter”还必须接受 在…上 处理程序。即使在得到意外字符串时抛出错误,Flow仍然希望您遵守该约定。

    好消息是:你 可以 让Flow知道您希望在某些事件上收到什么类型的参数。诀窍是指定 在…上 处理程序,如下所示:

    // @flow
    
    import EventEmitter from 'events'
    
    type CustomEventObj = {|
      pending: void,
      success: number,
      error: Error
    |}
    
    declare class MyEventEmitter extends EventEmitter {
      on(
        'pending',
        // Alternatively, you can pass in a callback like () => void
        // as the type here. Either works for the examples below.
        (
          e: null,
          ...args: Array<any>
        ) => void
      ): this;
    
      on(
        'success',
        (
          e: number,
          ...args: Array<any>
        ) => void
      ): this;
    
      on(
        'error',
        (
          e: Error,
          ...args: Array<any>
        ) => void
      ): this;
    
      // Just to satisfy inheritance requirements, but this should
      // throw at runtime.
      on(
        string,
        (
          e: mixed,
          ...args: Array<any>
        ) => void
      ): this;
    }
    
    let myInstance = new MyEventEmitter()
    
    myInstance.on('pending', () => {}) // works
    myInstance.on('success', (num: number) => {console.log(num * 2)}) // works
    myInstance.on('success', (badStr: string) => {console.log(badStr.length)}) // Error
    

    现在flow知道,例如,如果事件为“success”,那么回调应该接受一个数字(或包含数字的某种联合类型,如 string | number any )。

    如果您希望流到 只有 如果允许您传入特定事件,那么您将无法从EventEmitter类继承。(推理是这样的:如果您将MyEventMitter传递给传递其他事件的某个外部代码会怎么样?Flow将无法阻止这种情况发生,而外部代码也无法很好地知道除了“挂起”、“成功”或“失败”外,它不能传递其他事件)

    我已经在上发布了我的代码副本 GitHub for trying/reference/forking 。我会把它贴在 https://flow.org/try/ ,但我似乎无法理解EventEmitter或events$EventEmitter的继承。

    警告:我认为您可以为函数指定预期参数的原因是因为events$EventEmitter 在…上 函数具有 handler 参数 declared as Function type 。此类型是 little weird and mostly unchecked 。因此,在 在…上 处理程序适用于 现在 (Flow 0.69.0),它可能会与Flow或events$EventsEmitter typedef的未来版本中断。