0Pricing
TypeScript Academy · Урок

Обобщённый класс отправителя событий

Создайте с нуля повторно используемый типизированный отправитель событий

«Обобщённый класс отправителя событий» — бесплатный урок TypeScript Academy на CoddyKit. Это урок 3 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения TypeScript Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс TypeScript Academy содержит 4 уроков всего.

Цель: класс TypedEmitter с типизацией

Мы создадим повторно используемый класс TypedEmitter<E>, обобщённый относительно карты событий E, с полностью типизированными методами on, off и emit.

interface Events { login: { userId: string }; logout: void }
// class TypedEmitter<Events> will manage handlers safely.
console.log("building a typed emitter");

Псевдоним типа обработчика

Определите тип обработчика, параметризованный именем события, чтобы каждый слушатель получал правильную полезную нагрузку.

type Handler<E, K extends keyof E> = (payload: E[K]) => void;
// E[K] is the payload type for event K in map E.
console.log("handler typed by E[K]");

Карта слушателей

Храните обработчики, используя имена событий в качестве ключей. Отображаемый тип задаёт для каждого ключа массив обработчиков с правильными типами.

class TypedEmitter<E> {
  private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
}
console.log("listeners map ready");

Реализация on

Метод on добавляет обработчик в массив соответствующего события, создавая массив при первом использовании.

class TypedEmitter<E> {
  private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
  on<K extends keyof E>(event: K, handler: (p: E[K]) => void): void {
    (this.listeners[event] ||= []).push(handler);
  }
}
console.log("on implemented");

Реализация emit

Метод emit находит обработчики события и вызывает каждый из них, передавая полезную нагрузку с правильным типом.

class TypedEmitter<E> {
  private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
  on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
  emit<K extends keyof E>(e: K, payload: E[K]): void {
    (this.listeners[e] || []).forEach(h => h(payload));
  }
}
console.log("emit implemented");

Реализация off

Метод off удаляет ранее зарегистрированный обработчик, отфильтровывая его из массива.

class TypedEmitter<E> {
  private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
  on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
  off<K extends keyof E>(e: K, h: (p: E[K]) => void) {
    this.listeners[e] = (this.listeners[e] || []).filter(x => x !== h);
  }
}
console.log("off implemented");

Объединение класса

Ниже приведён полный класс со всеми тремя методами, обобщённый относительно любой предоставленной вами карты событий.

class TypedEmitter<E> {
  private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
  on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
  off<K extends keyof E>(e: K, h: (p: E[K]) => void) { this.listeners[e] = (this.listeners[e] || []).filter(x => x !== h); }
  emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
console.log("TypedEmitter complete");

Использование TypedEmitter

Создайте экземпляр с конкретной картой событий. Теперь on и emit полностью типизированы для этих событий.

class TypedEmitter<E> {
  private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
  on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
  emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
interface Events { login: { userId: string } }
const bus = new TypedEmitter<Events>();
bus.on("login", p => console.log("hi", p.userId));
bus.emit("login", { userId: "u1" });

Ошибки типов обнаруживаются

Класс отклоняет неизвестные события и неправильные полезные нагрузки во время компиляции, как это делали отдельные функции.

class TypedEmitter<E> {
  private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
  on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
  emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
interface Events { ping: { at: number } }
const bus = new TypedEmitter<Events>();
// bus.emit("pong", { at: 1 }); // Error
bus.emit("ping", { at: 1 });
console.log("typed bus");

Несколько обработчиков для одного события

Поскольку каждому событию соответствует массив, можно зарегистрировать много обработчиков, и все они будут вызваны при выполнении emit.

class TypedEmitter<E> {
  private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
  on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
  emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
interface Events { tick: { n: number } }
const bus = new TypedEmitter<Events>();
bus.on("tick", p => console.log("a", p.n));
bus.on("tick", p => console.log("b", p.n));
bus.emit("tick", { n: 7 });

Основа для дальнейшего расширения

Этот класс — надёжная основа. В следующем уроке мы улучшим вывод типов аргументов слушателей и добавим семантику once.

class TypedEmitter<E> {
  private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
  on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
  emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
const bus = new TypedEmitter<{ go: void }>();
bus.on("go", () => console.log("go!"));
bus.emit("go", undefined as void);

Быстрая проверка: обобщённый класс-эмиттер

Проверьте своё понимание обобщённого класса-эмиттера событий.

Итоги: обобщённый класс-эмиттер событий

Вы создали TypedEmitter<E> с хранилищем слушателей на основе отображаемого типа и полностью типизированными методами on, off и emit, получив повторно используемую типобезопасную модель публикации и подписки для любой карты событий.

class TypedEmitter<E> {
  private listeners: { [K in keyof E]?: Array<(p: E[K]) => void> } = {};
  on<K extends keyof E>(e: K, h: (p: E[K]) => void) { (this.listeners[e] ||= []).push(h); }
  emit<K extends keyof E>(e: K, p: E[K]) { (this.listeners[e] || []).forEach(h => h(p)); }
}
const bus = new TypedEmitter<{ hi: { name: string } }>();
bus.on("hi", p => console.log(p.name));
bus.emit("hi", { name: "Ada" });

Часто задаваемые вопросы

Урок «Обобщённый класс отправителя событий» бесплатный?

Да — полный текст урока «Обобщённый класс отправителя событий» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс TypeScript Academy, подпишись на CoddyKit PRO. Курс TypeScript Academy содержит 4 уроков всего.

Чему я научусь в уроке «Обобщённый класс отправителя событий»?

Создайте с нуля повторно используемый типизированный отправитель событий Ты практикуешь TypeScript Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать TypeScript Academy?

Предыдущий опыт не требуется. TypeScript Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 3 из 4.

Сколько времени занимает урок «Обобщённый класс отправителя событий»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке TypeScript Academy?

Да. Каждый урок TypeScript Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Типизация карт событий
  2. Типобезопасные emit и on
  3. Обобщённый класс отправителя событий
  4. Вывод аргументов обработчиков
← Назад к TypeScript Academy