← Усі задачіTypeScript · Middle

Типізований EventEmitter

Клас Emitter<Events> отримує опис подій: new Emitter<{ login: { userId: number }; message: string }>(). Метод on(event, handler) підписує обробник і повертає функцію відписки, emit(event, payload) викликає обробники цієї події.

Зробіть типи точними: назва події - лише одна з ключів Events, а тип payload в обробнику та в emit береться з опису саме цієї події.

Тести (7)

Тести типів перевіряє компілятор TypeScript у режимі strict. Expect<Equal<A, B>> проходить, коли типи збігаються точно, а // @ts-expect-error - коли наступний рядок не компілюється. Перший запуск завантажує компілятор, тож триває кілька секунд.

  1. типи
    type E = { login: { userId: number }; message: string };
    const e = new Emitter<E>();
    e.on('login', (payload) => {
      type _ = Expect<Equal<typeof payload, { userId: number }>>;
    });
  2. типи
    type E = { login: { userId: number }; message: string };
    const e = new Emitter<E>();
    // @ts-expect-error - message carries a string
    e.emit('message', 42);
  3. типи
    type E = { login: { userId: number }; message: string };
    const e = new Emitter<E>();
    // @ts-expect-error - there is no such event
    e.on('signup', () => {});
  4. const e = new Emitter();
    const a = fn();
    const b = fn();
    e.on('message', a);
    e.on('message', b);
    e.emit('message', 'hi');
    expect(a).toHaveBeenCalledWith('hi');
    expect(b).toHaveBeenCalledWith('hi');
  5. const e = new Emitter();
    const h = fn();
    const off = e.on('login', h);
    e.emit('login', { userId: 1 });
    off();
    e.emit('login', { userId: 2 });
    expect(h).toHaveBeenCalledTimes(1);
    expect(h).toHaveBeenLastCalledWith({ userId: 1 });
  6. const e = new Emitter();
    expect(() => e.emit('nobody-listens', 1)).not.toThrow();
  7. const e = new Emitter();
    const a = fn();
    const b = fn();
    e.on('x', a);
    e.on('y', b);
    e.emit('x', 1);
    expect(a).toHaveBeenCalledTimes(1);
    expect(b).toHaveBeenCalledTimes(0);