1

Here's a design of an event emitter:

type EventMap = Record<string, any>;

type EventKey<T extends EventMap> = string & keyof T;
type EventReceiver<T> = (params: T) => void;

interface Emitter<T extends EventMap> {
  on<K extends EventKey<T>>
    (eventKey: K, fn: EventReceiver<T[K]>): void;
  emit<K extends EventKey<T>>
    (eventKey: K, params: T[K]): void;
}

// example usage
const emitter = eventEmitter<{
  data: Buffer | string;
  end: undefined;
}>();

// OK!
emitter.on('data', (x: string) => {});

// Error! 'string' is not assignable to 'number'
emitter.on('data', (x: number) => {});

The design works but two things I don't understand:

  1. EventKey: why does it need to add a string & here?

  2. Why do we need generics K with on and emit methods, can't the type for eventKey param simply be keyof T?

Many thanks in advance.

1 Answer 1

2

EventKey: why does it need to add a string & here?

The intersection string & ... is used to make sure that only keys that are actually of type string can be used to define the EventKey type.

Even though EventMap is defined as Record<string,any> you can still assign an object with non-string properties to it:

const a: EventMap = {
  foo: 'bar',
  100: 'baz', // whoops, number property
}

Why? Because objects can have excess properties.

So in such a case, string & keyof T would result to never for rogue properties that are not of type string.

Can't the type for eventKey param simply be keyof T?

This is to allow keys defined as union types, which do extend string (See also this answer):

type EnumK = 'alpha' | 'beta'
type MyObject = {[P in EnumK]: string }

const obj: SomeObj = {
  alpha: 'foo',
  beta: 'bar'
}

Here the type of obj's keys is an extension of string, namely the union between the string literal types alpha and beta. So by using K extends ... you capture this relation and ensure type safety in type indexing T[K].

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.