TypeScript index types constraint with "callback type" works incorrectly?

Viewed 82

I can't understand why this will give me an error (with compiler option "strict": true).

class EventX {
  public type: string

  constructor(type: string) {
    this.type = type
  }
}

class ClickEvent extends EventX {
  public x: number

  constructor(type: string, x: number) {
    super(type);
    this.x = x;
  }
}

interface Events {
  [eventType: string]: (event: EventX) => any

  click: (e: ClickEvent) => any  // TS2411: Property 'click' of type '(e: ClickEvent) => any' is not assignable to string index type '(event: EventX) => any'.
}

By the way this works fine

interface Events {
  [eventType: string]: EventX

  click: ClickEvent
}

So I think is this part (event: EventX) => any cause the problem. But what's wrong with this? How can I fix it?

2 Answers

The problem is that an index signature must include all possible types that the values may have:

interface Events {
    click: (event: ClickEvent) => any;
    [eventType: string]: ((event: EventX) => any) | ((event: ClickEvent) => any);
}

This is due to ALL keys being included in the index signature [x: string], thus all are checked for conformance - and since (event: ClickEvent) => any is not assignable to (event: EventX) => any it throws an error

Edit: Mihályi Zoltán has a better explanation.

The difference between the two examples is "variance".

In the second example events['click' as string] is just an EventX, which is OK. But in the first example events['click' as string](new EventX()) should cause an error, and the TypeScript error prevents this.

Edit: Don't ask me why events['click' as string] = new EventX() is not prevented by TypeScript as it does for functions

Related