Как реализовать инверсию зависимости через события?
Что проверяют
Отличаете ли вы принцип инверсии зависимостей от внедрения зависимостей и понимаете ли, как событие разворачивает направление связи. Следом просят показать это на своём фреймворке и спрашивают, когда события вредят.
Как отвечать
Без событий модуль корзины сам вызывает аналитику и уведомления, то есть зависит от них. С событиями корзина объявляет контракт cart:item-added с типом данных и публикует его, а аналитика и уведомления подписываются. Теперь они зависят от контракта, которым владеет корзина, а корзина про них не знает. Это и есть инверсия: высокоуровневый модуль владеет абстракцией, детали подстраиваются под неё.
Инструменты на фронтенде:
- колбэки в пропсах:
onChangeв React,defineEmitsиemitво Vue,@Output()или функцияoutput()в Angular. Дочерний компонент сообщает о событии и не знает, кто его обработает; EventTargetиCustomEvent: встроены в браузер, отEventTargetможно наследоваться. Отписку удобно делать черезaddEventListener(type, fn, { signal })и одинAbortController;- шина событий: маленькая библиотека вроде mitt или свой emitter. В TypeScript события описывают картой типов
{ 'cart:item-added': { id: string } }, и опечатка в имени становится ошибкой компиляции; - микрофронтенды:
CustomEventнаwindowкак общий контракт между приложениями.
Назовите и цену: поток управления становится неявным. По коду не видно, кто отреагирует на событие, поэтому общее состояние лучше держать в сторе, а события оставить для уведомлений «что-то произошло».
Где спотыкаются
Не отписываются. Компонент размонтирован, а обработчик живёт в шине, держит замыкание и срабатывает на чужих экранах. В React отписка идёт в функции очистки useEffect.
Передают метод класса как this.handleEvent без привязки. Внутри обработчика this оказывается undefined, нужна стрелочная функция или bind.
Строят всё приложение на глобальной шине. Через полгода цепочки «событие вызывает событие» не отладить, а порядок срабатывания обработчиков становится скрытой зависимостью.