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? Делитесь в комментариях!