最新消息:Welcome to the puzzle paradise for programmers! Here, a well-designed puzzle awaits you. From code logic puzzles to algorithmic challenges, each level is closely centered on the programmer's expertise and skills. Whether you're a novice programmer or an experienced tech guru, you'll find your own challenges on this site. In the process of solving puzzles, you can not only exercise your thinking skills, but also deepen your understanding and application of programming knowledge. Come to start this puzzle journey full of wisdom and challenges, with many programmers to compete with each other and show your programming wisdom! Translated with DeepL.com (free version)

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

matteradmin3PV0评论

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

Post a comment

comment list (0)

  1. No comments so far