Vue-костыли и странности: от классики до дичи (часть 3)
5. Забытые глобальные ивенты, или «Приложение, которое съело память»
Как часто встречается: Постоянно при работе с глобальными событиями (window resize, scroll, keydown).
Схема: В onMounted вешают window.addEventListener('keydown', handleEsc), а отписаться в onUnmounted «забывают».
В чем боль: Компонент уничтожается, но функция-обработчик навсегда остается в памяти браузера, намертво удерживая за собой весь контекст компонента через замыкание. Каждое открытие модалки плодит новый слушатель. Через час активной работы вкладка вашего приложения втихаря отъедает гигабайты оперативки, а интерфейс начинает безбожно лагать.
Как надо: Всегда убирать за собой игрушки. При этом в removeEventListener нужно передавать ту же самую именованную функцию по ссылке, а не анонимную, иначе браузер просто не поймет, кого нужно удалить.
``` const handleKeyDown = (e) => { if (e.key === 'Escape') closeModal(); };
onMounted(() => { window.addEventListener('keydown', handleKeyDown); });
// Чистим за собой: onUnmounted(() => { window.removeEventListener('keydown', handleKeyDown); }); ```
Важно: Передавать в removeEventListener нужно именно именованную функцию (handleKeyDown), а не анонимную, иначе браузер просто не поймет, какого именно слушателя нужно удалить.
``` // такой вариант не сработает window.removeEventListener('keydown', (e) => { if (e.key === 'Escape') closeModal(); }); ```
Альтернатива: В экосистеме Vue 3 есть библиотека VueUse. Вместо ручных слушателей можно использовать готовый и безопасный composable-метод useEventListener(window, 'keydown', handleKeyDown), который сам автоматически отпишется при уничтожении компонента.
6. Мина замедленного действия: сайд-эффекты в computed
Как часто встречается: Когда разработчик пытается «сэкономить» на вотчерах и запихать всё в одно место.
Схема: Внутри computed не просто вычисляют значение, но и попутно дергают другие реактивные переменные или отправляют запросы на бэкенд. «А что, удобненько — данные изменились, и всё нужное сразу сделалось!»
``` const filteredList = computed(() => { const result = list.value.filter(item => item.active); totalActiveCount.value = result.length; // Сайд-эффект! api.sendAnalytics(); // Еще один! return result; }); ```
В чем боль: - Вычисляемые свойства ленивы. Код внутри computed выполняется только тогда, когда его значение кто-то читает (например, выводит в шаблоне). Если блок с этим computed скроется по v-if, функция просто перестанет вызываться — и ваши счетчики вместе с аналитикой внезапно сломаются.
- Реактивный шторм. Если внутри computed нечаянно изменить переменную, от которой это же вычисление зависит, вы спровоцируете бесконечный цикл. Приложение намертво зависнет, а консоль взорвется ошибкой Maximum call stack size exceeded.
Когда споткнемся: При первом же рефакторинге интерфейса, когда ленивый computed выпадет из зоны видимости шаблона и заберет с собой всю «завязаную» на него бизнес-логику.
Как надо: Запомнить правило: computed — это всегда чистая функция (входные данные -> результат), у неё не должно быть побочных эффектов. Вычисления оставляем в computed, а реакцию на изменения данных выносим в watch или watchEffect.
``` // Только чистые вычисления: const filteredList = computed(() => list.value.filter(item => item.active));
// Сайд-эффекты отдельно: watch(filteredList, (newList) => { totalActiveCount.value = newList.length; api.sendAnalytics(); }, { immediate: true }); ```
А у вас есть "любимый" хак в Vue? Делитесь в комментариях!