类型缺少属性,但类型必需

时间:2020-07-06 23:39:45

标签: javascript typescript

我正在尝试在TypeScript上实现简单的事件调度程序。

我的自定义类型的声明如下:

events.d.ts

type AppEvent = { [key: string]: any }

type AppEventListener<T> = <T extends AppEvent>(event: T) => void;

当我在此函数中传递任何对象时

   public addListener<T extends AppEvent>(event: T, listener: AppEventListener<T>)

我收到以下错误:

Argument of type '(event: TestEvent) => void' is not assignable to parameter of type 'AppEventListener<typeof TestEvent>'.
   Types of parameters 'event' and 'event' are incompatible.
     Type 'T' is not assignable to type 'TestEvent'.
       Property 'name' is missing in type 'AppEvent' but required in type 'TestEvent'.

因此,此类型AppEvent不能具有任何其他属性。如何使其运作?任何帮助表示赞赏。

完整代码示例:

class EventDispatcher {
  private listeners: Map<AppEvent, Array<AppEventListener<AppEvent>>>

  constructor() {
    this.listeners = new Map();
  }

  public dispatch<T extends AppEvent>(event: T): void {
    const listeners = this.listeners.get(event.constructor);
    if (listeners) {
      listeners.forEach((callback) => {
        callback(event);
      });
    }
  }

  public addListener<T extends AppEvent>(event: T, listener: AppEventListener<T>): void {
    let listeners = this.listeners.get(event);
    if (!listeners) {
      listeners = [listener];
      this.listeners.set(event, listeners);
    } else {
      listeners.push(listener);
    }
  }
}

class TestEvent {
  public name: string = ''
}

const dispatcher = new EventDispatcher();
const listener = (event: TestEvent) => {
  console.dir(event);
};
dispatcher.addListener(TestEvent, listener);

const event = new TestEvent('name');
dispatcher.dispatch(event);

1 个答案:

答案 0 :(得分:2)

看起来您应该使AppEventLister为泛型类型,以引用非泛型函数,如下所示:

type AppEventListener<T> = (event: T) => void;

您说的是“ AppEventListener<T>将发生类型为T的事件”。那就是你想要的,对吧?


您的原始定义是一个泛型类型,它引用一个泛型函数,具有两个同名的不同泛型参数,等效于

type AppEventListenerBad<T> = <U>(event: U) => void;

(重命名不会更改类型;它只会使类型参数名称阴影更清楚地说明了什么)。这意味着“ AppEventListener<T>将完全忽略T并接受任何事件键入呼叫者要指定的U”,这不太可能是您的意思。这就是您的错误出处:(event: TestEvent) => void无法分配给<U>(event: U) => void


执行此操作后,还会解决许多其他问题,但是它们可能会带来其他问题。一个主要的方面是standard library typings for Map表示从K类型的键到V类型的值的非常基本的映射。它没有办法说K的某些子类型的键映射到V的以编程方式确定的子类型的值。在您的情况下,您可以为使用Map的方式创建一个新界面:

interface AppEventListenerMap {
  get<T>(x: T): Array<AppEventListener<T>> | undefined;
  set<T>(x: T, val: Array<AppEventListener<T>>): void;
}

然后在EventDispatcher中,将listeners属性设置为类型AppEventListenerMap的值:

class EventDispatcher {
  private listeners: AppEventListenerMap;

  constructor() {
    this.listeners = new Map();
  }

大多数情况下,这将使您的代码示例正常工作,但有时您似乎使用event作为键,而其他时候似乎使用event.constructor作为键。这种不一致似乎是错误的,您可能需要准确确定类型关系才能使其正常工作。

无论如何,希望这会有所帮助;祝你好运!

Playground link to code