Why doesn't typescript narrow this generic type properly?

Viewed 50

I am using socket.io, so I'm working with an event types object that looks like this:

// these are the possible events. the event name is the key and the event function type is the type
interface Events {
    a: () => void;
    b: (x: number) => void;
}

The possible events that the socket can emit are a and b. When a is emitted, it's of type () => void, and when b is emitted, it's of type (x: number) => void.

Now I want to make a map where I store multiple callbacks for each event type.

// this gets the event function type based on the event name
type SpecificListenerType<T extends keyof Events> = Pick<Events, T>[T];

// show that it works
let aa: SpecificListenerType<'a'>; // type is () => void
let bb: SpecificListenerType<'b'>; // type is (x: number) => void

// this is a map that stores a list of functions for each event name
type ListenerMap = {
    [K in keyof Events]: Array<SpecificListenerType<K>>;
};

// initialize the map to empty lists
const listenerMap: ListenerMap = {a: [], b: []};

// show that it works
let aaa = listenerMap['a']; // type is Array<() => void>
let bbb = listenerMap['b']; // type is Array<(x: number) => void>

That should allow me to map a to a list of void functions with no arguments and b to a list of void functions that have 1 number argument.

Now I want to write the function that removes items from this map.

// function to remove one item from the map
const off = <T extends keyof Events>(
    event: T,
    listener: SpecificListenerType<T>,
) => {
  // what can I do here?
  listenerMap[event] = listenerMap[event].filter((registeredListener) => {
    return registeredListener !== listener;
  });
};

But I cannot assign to the map because the types don't work out. I thought the SpecificListenerType<T> would do the trick here, but I'm not sure why it doesn't. listenerMap[event] should resolve to a single function type, no?

TS Playground link

0 Answers
Related