最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Property is missing in type but required in type - Stack Overflow

programmeradmin6浏览0评论

I'm trying to implement simple event dispatcher on TypeScript.

My custom types are declared like this:

events.d.ts:

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

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

When I pass any object in this function

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

I get the following error:

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

So, this type AppEvent cannot have any additional properties. How to make it work? Any help appreciated.

Full code example:

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);

I'm trying to implement simple event dispatcher on TypeScript.

My custom types are declared like this:

events.d.ts:

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

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

When I pass any object in this function

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

I get the following error:

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

So, this type AppEvent cannot have any additional properties. How to make it work? Any help appreciated.

Full code example:

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);

Share Improve this question asked Jul 6, 2020 at 23:39 Dmitry SolovovDmitry Solovov 4331 gold badge3 silver badges9 bronze badges 2
  • 2 First of all, type AppEventListener<T> = <T extends AppEvent>(event: T) => void; isn't right. You have two different T's there. You probably want either type AppEventListener<T extends AppEvent> = (event: T) => void; or type AppEventListener = <T extends AppEvent>(event: T) => void;. – Alex Wayne Commented Jul 7, 2020 at 0:27
  • Thank you. First variant is preferable then because it allows to require the same type for both arguments in addListener method – Dmitry Solovov Commented Jul 7, 2020 at 10:39
Add a ment  | 

1 Answer 1

Reset to default 4

It looks like you should make AppEventLister a generic type referring to a non-generic function, like this:

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

You're saying "an AppEventListener<T> will take an event of type T". That's what you want, right?


Your original definition was a generic type referring to a generic function, with two different generic parameters of the same name, equivalent to

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

(the rename doesn't change the type; it just makes makes more clear what was happening with type parameter name shadowing) meaning "an AppEventListener<T> will ignore T entirely and take an event of any type U that the caller wants to specify", which is unlikely what you mean. And that's where your error is ing from: (event: TestEvent) => void is not assignable to <U>(event: U) => void.


Once you do this, a lot of other issues are solved, but they might bring up other problems. A major one is that the standard library typings for Map represent a very basic mapping from keys of type K to values of type V. It does not have a way to say that keys of certain subtypes of K map to values of programmatically determined subtypes of V. In your case you could make a new interface for the way you are using Map:

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

And then in EventDispatcher you make the listeners property a value of type AppEventListenerMap:

class EventDispatcher {
  private listeners: AppEventListenerMap;

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

That will mostly make your code example work, except for the fact that you seem to be using event as the key sometimes, and using event.constructor as the key other times. That inconsistency seems wrong, and you probably need to decide exactly the type relationship to get it working properly.

Anyway, hopefully this helps; good luck!

Playground link to code

发布评论

评论列表(0)

  1. 暂无评论