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

saa*_*adq 9 javascript eventemitter flowtype flow-typed

举个例子,假设我有一个类只有发出三种可能的事件-  '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
}
Run Code Online (Sandbox Code Playgroud)

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

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?
Run Code Online (Sandbox Code Playgroud)

我不希望有一个索引器属性,CustomEventObj因为不会杀死只有3个可能事件的点?

任何帮助,将不胜感激.

Jam*_*aus 3

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

因为您要扩展 EventEmitter 类,所以您明确声明您的类将执行与 EventEmitter 相同的操作。因此,由于 EventEmitter 的on处理程序将接受 any 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
Run Code Online (Sandbox Code Playgroud)

现在,流程了解,例如,如果事件是“成功”,则回调应该接受一个数字(或某种包含数字的联合类型,例如 或string | numberany

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

我已经在 GitHub 上发布了我的代码副本,用于尝试/参考/分叉。我将其发布在https://flow.org/try/上,但我似乎无法让它理解来自 EventEmitter 或 events$EventEmitter 的继承。

警告:我认为您可以为函数指定预期参数的原因是因为 events$EventEmitter 的on函数将handler参数声明为Functiontype。这种类型有点奇怪,而且大多未经检查on因此,虽然这种在处理程序上进行多重声明的技术目前有效(Flow 0.69.0),但它可能会因 Flow 或 events$EventsEmitter typedef 的未来版本而中断。