Reputation: 3464
I'm trying to subscribe to an event, but TSC gives me an error (ts playground).
interface Event {
[key: string]: string;
}
interface EventHandler {
(event: Event): void;
}
interface Config {
name: string;
}
function on (eventName: string, eventHandler: EventHandler) {
console.log(eventName, eventHandler)
}
function configHandler (config: Config) {
console.log(config)
}
on('config', configHandler) // TSC ERROR HERE
Error
Argument of type '(config: Config) => void' is not assignable to parameter of type 'EventHandler'. Types of parameters 'config' and 'event' are incompatible. Property 'name' is missing in type 'Event' but required in type 'Config'.
What would be your fix for this issue?
Upvotes: 0
Views: 232
Reputation: 33111
This is because
on('config', configHandler) // TSC ERROR HERE
expects as a second argument EventHandler
. Arguments of EventHandler
and configHandler
are not assignable to each other.
declare var config: Config;
declare var ev: Event;
config = ev // error
ev = config // error
Btw, Event
is defined in TS globals. It is a built-in type.
Are you sure you want to extend it by index signature?
In order to make it work, you should extend Event
type by Config
:
interface Event extends Config {
[key: string]: string;
}
Anyway, this seems to be not type safe.
Take a look on this example:
const enum Events {
foo = "foo",
bar = "bar",
baz = "baz",
}
/**
* Single sourse of true
*/
interface EventMap {
[Events.foo]: { foo: number };
[Events.bar]: { bar: string };
[Events.baz]: { baz: string[] };
}
type Values<T> = T[keyof T];
// credits goes to https://stackoverflow.com/a/50375286
type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
k: infer I
) => void
? I
: never;
type EmitRecord = {
[P in keyof EventMap]: (name: P, data: EventMap[P]) => void;
};
type ListenRecord = {
[P in keyof EventMap]: (
name: P,
callback: (arg: EventMap[P]) => void
) => void;
};
type MakeOverloadings<T> = UnionToIntersection<Values<T>>;
type Emit = MakeOverloadings<EmitRecord>;
type Listen = MakeOverloadings<ListenRecord>;
const emit: Emit = <T,>(name: string, data: T) => {};
emit(Events.bar, { bar: "1" });
emit(Events.baz, { baz: ["1"] });
emit("unimplemented", { foo: 2 }); // expected error
const listen: Listen = (name: string, callback: (arg: any) => void) => {};
listen(Events.baz, (arg /* { baz: string[] } */) => {});
listen(Events.bar, (arg /* { bar: string } */) => {});
Above example is much safer.
Here, in my blog, you can find more explanation
Upvotes: 1