在使用 Flow 扩展 EventEmitter 的 class 中限制 `eventName` 的类型?

Limiting the type of `eventName` in a class that extends EventEmitter with Flow?

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

以下是我尝试建模的方式:

// @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 个可能事件的意义吗?

如有任何帮助,我们将不胜感激。

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

因为您正在扩展 EventEmitter class,所以您明确声明您的 class 将执行与 EventEmitter 相同的操作。因此,由于 EventEmitter 的 on 处理程序将接受任何 string,您的 "MyEventEmitter" 也必须接受 on 处理程序中的任何字符串。即使你只是抛出一个错误 if/when 你得到一个意想不到的字符串,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

现在流理解如果事件是 "success",那么回调应该接受一个数字(或一些包含数字的联合类型,如 string | numberany) .

如果您希望 Flow 允许您传递特定事件,那么您将无法从 EventEmitter class 继承。 (推理是这样的:如果您将 MyEventEmitter 传递给一些传递其他事件的外部代码怎么办?Flow 无法阻止这种情况的发生,并且外部代码也无法知道它无法传递'pending'、'success' 或 'failure')

以外的事件

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

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