manidos
manidos

Reputation: 3464

Need help adding types to event bus methods

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

Answers (1)

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

Related Questions