Обобщённый класс отправителя событий
Создайте с нуля повторно используемый типизированный отправитель событий
«Обобщённый класс отправителя событий» — бесплатный урок 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 — локальная установка не требуется.
Все уроки этого курса
- Типизация карт событий
- Типобезопасные emit и on
- Обобщённый класс отправителя событий
- Вывод аргументов обработчиков