【问题标题】:Limiting the type of `eventName` in a class that extends EventEmitter with Flow?在使用 Flow 扩展 EventEmitter 的类中限制“eventName”的类型?
【发布时间】:2018-08-30 21:05:34
【问题描述】:

例如,假设我有一个类只发出三个可能的事件 - 'pending''success''failure'。此外,eventHandler 中接收的参数类型取决于发出的事件 -

  • 如果'pending'eventHandler 不接收任何参数
  • 如果'success'eventHandler 收到number
  • 如果'failure'eventHandler 收到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 个可能事件的意义吗?

任何帮助将不胜感激。

【问题讨论】:

    标签: javascript flowtype eventemitter flow-typed


    【解决方案1】:

    嗯,我有好消息和坏消息。好消息是,您将能够让 Flow 了解作为参数传递给 on 处理程序的内容。坏消息是,您将无法告诉 Flow 不允许您期望的事件之外的其他事件。

    因为您正在扩展 EventEmitter 类,所以您明确声明您的类将做与 EventEmitter 相同的事情。因此,由于 EventEmitter 的 on 处理程序将接受任何 string,因此您的“MyEventEmitter”也必须接受 on 处理程序中的任何字符串。即使您只是在收到意外字符串时抛出错误,Flow 仍然希望您遵守该合同。

    现在有个好消息:您可以让 Flow 知道您期望在某些事件中收到什么样的参数。诀窍是指定on 处理程序的多个声明,如下所示:

    // @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 明白,如果事件是“成功”,例如,那么回调应该接受一个数字(或一些包含数字的联合类型,如 string | numberany)。

    如果您希望 Flow 允许您传递特定事件,那么您将无法从 EventEmitter 类继承。 (推理是这样的:如果你将 MyEventEmitter 传递给一些传递其他事件的外部代码怎么办?Flow 无法阻止这种情况发生,外部代码也无法知道它不能传递除了“未决”、“成功”或“失败”之外的事件)

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

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

    【讨论】:

      猜你喜欢
      • 2021-12-14
      • 2017-01-01
      • 2017-11-23
      • 2016-10-15
      • 1970-01-01
      • 1970-01-01
      • 2013-03-20
      • 2018-03-01
      • 2018-04-11
      相关资源
      最近更新 更多