Типобезопасные emit и on
Обеспечивайте правильные данные при отправке событий и прослушивании
«Типобезопасные emit и on» — бесплатный урок TypeScript Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения TypeScript Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс TypeScript Academy содержит 4 уроков всего.
Сигнатура типобезопасного emit
Сделайте emit обобщённым относительно имени события K. Тогда параметр полезной нагрузки будет иметь тип Events[K], поэтому имя и полезная нагрузка должны соответствовать друг другу.
interface Events { login: { userId: string }; logout: void }
function emit<K extends keyof Events>(event: K, payload: Events[K]) {
console.log("emit", event, JSON.stringify(payload));
}
emit("login", { userId: "u1" });Неправильная полезная нагрузка вызывает ошибку компиляции
Если полезная нагрузка не соответствует имени события, TypeScript отклоняет её. Обобщённый тип связывает эти два параметра.
interface Events { login: { userId: string } }
function emit<K extends keyof Events>(event: K, payload: Events[K]) {}
// emit("login", { id: "u1" }); // Error: id is not userId
emit("login", { userId: "u1" });
console.log("ok");Неизвестные имена событий отклоняются
Поскольку K extends keyof Events, нельзя отправить событие, которого нет в карте.
interface Events { login: { userId: string } }
function emit<K extends keyof Events>(event: K, payload: Events[K]) {}
// emit("signup", { userId: "x" }); // Error: "signup" not a key
emit("login", { userId: "x" });
console.log("only known events");Типизированные обработчики с подпиской
Метод on регистрирует обработчик, аргумент которого имеет в точности тип Events[K], поэтому обработчики получают полезные нагрузки с правильными типами.
interface Events { login: { userId: string }; logout: void }
function on<K extends keyof Events>(event: K, handler: (payload: Events[K]) => void) {
console.log("subscribed to", event);
}
on("login", (p) => console.log(p.userId));Тип аргумента обработчика выводится автоматически
Не нужно явно указывать тип параметра обработчика: TypeScript выводит его из имени события, обеспечивая полное автодополнение внутри тела обработчика.
interface Events { message: { text: string } }
function on<K extends keyof Events>(event: K, handler: (p: Events[K]) => void) {}
on("message", (p) => {
// p is { text: string }
console.log(p.text.toUpperCase());
});Обработка событий без полезной нагрузки
Для событий типа void обработчик просто не использует никаких аргументов. Его можно вызвать без данных.
interface Events { logout: void }
function on<K extends keyof Events>(event: K, handler: (p: Events[K]) => void) {}
on("logout", () => console.log("logged out"));Сочетание emit и подписки
Вместе emit и on образуют типизированную модель публикации и подписки. Отправка события передаёт его обработчикам полезную нагрузку с правильным типом.
interface Events { ping: { at: number } }
type Handler<K extends keyof Events> = (p: Events[K]) => void;
const handlers: { ping: Handler<"ping">[] } = { ping: [] };
function on<K extends keyof Events>(e: K, h: Handler<K>) { handlers[e].push(h as Handler<"ping">); }
function emit<K extends keyof Events>(e: K, p: Events[K]) { handlers[e].forEach(h => h(p as Events["ping"])); }
on("ping", p => console.log("at", p.at));
emit("ping", { at: 5 });Ограничение позиции полезной нагрузки
Обобщённый тип гарантирует, что аргумент полезной нагрузки проверяется относительно выбранного события, обнаруживая несоответствия в месте вызова, а не глубоко внутри обработчика.
interface Events { add: { x: number; y: number } }
function emit<K extends keyof Events>(e: K, p: Events[K]) {
return e;
}
emit("add", { x: 1, y: 2 });
// emit("add", { x: 1 }); // Error: y missing
console.log("payload checked");Автодополнение имён событий
Поскольку первый параметр имеет тип keyof Events, редакторы предлагают допустимые имена событий по мере ввода вызова.
interface Events { open: void; close: void }
function emit<K extends keyof Events>(e: K, p: Events[K]) {}
// Typing emit(" suggests "open" and "close"
emit("open", undefined as void);
console.log("autocompleted names");Почему здесь необходимы обобщённые типы
Без обобщённого типа понадобились бы перегрузки для каждого события. Одна обобщённая сигнатура автоматически масштабируется на любое количество событий.
interface Events { a: { x: number }; b: { y: string } }
function emit<K extends keyof Events>(e: K, p: Events[K]) { return p; }
console.log(emit("a", { x: 1 }), emit("b", { y: "hi" }));Повторно используемый шаблон
Шаблон emit<K>(event, payload) и on<K>(event, handler) лежит в основе любого типобезопасного эмиттера. В следующем уроке мы оформим его в виде класса.
interface Events { tick: { n: number } }
function emit<K extends keyof Events>(e: K, p: Events[K]) { return e; }
function on<K extends keyof Events>(e: K, h: (p: Events[K]) => void) { return e; }
console.log(emit("tick", { n: 1 }), on("tick", p => p.n));Быстрая проверка: типобезопасные emit и подписка
Проверьте своё понимание типобезопасных emit и on.
Итоги: типобезопасные emit и подписка
Вы создали обобщённые сигнатуры emit и on, в которых полезная нагрузка имеет тип Events[K]. Поэтому имена событий дополняются автоматически, неправильные полезные нагрузки вызывают ошибки компиляции, а типы аргументов обработчиков выводятся автоматически.
interface Events { login: { userId: string } }
function emit<K extends keyof Events>(e: K, p: Events[K]) { return e; }
console.log(emit("login", { userId: "u1" }));Часто задаваемые вопросы
Урок «Типобезопасные emit и on» бесплатный?
Да — полный текст урока «Типобезопасные emit и on» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс TypeScript Academy, подпишись на CoddyKit PRO. Курс TypeScript Academy содержит 4 уроков всего.
Чему я научусь в уроке «Типобезопасные emit и on»?
Обеспечивайте правильные данные при отправке событий и прослушивании Ты практикуешь TypeScript Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать TypeScript Academy?
Предыдущий опыт не требуется. TypeScript Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Типобезопасные emit и on»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке TypeScript Academy?
Да. Каждый урок TypeScript Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Типизация карт событий
- Типобезопасные emit и on
- Обобщённый класс отправителя событий
- Вывод аргументов обработчиков