Утечка памяти — ситуация, при которой объекты остаются в памяти после того, как они перестали использоваться. В браузерной среде это приводит к постепенному увеличению потребления памяти, снижению производительности и, в крайних случаях, к зависанию страницы.
В библиотеке Tippy.js утечки чаще всего связаны с жизненным циклом tooltip-компонентов, DOM-узлами и привязанными обработчиками событий.
Каждый вызов tippy() создает экземпляр (instance),
содержащий:
Если экземпляр не уничтожен корректно, все эти ссылки остаются в памяти.
При удалении элемента из DOM без вызова
instance.destroy():
const instance = tippy(element);
// элемент удаляется из DOM
element.remove();
Экземпляр продолжает существовать, удерживая:
Решение:
instance.destroy();
element.remove();
При динамическом рендеринге (например, в списках или таблицах):
items.forEach(item => {
tippy(item);
});
При повторном рендере создаются новые инстансы без удаления старых.
Последствия:
Решение:
instances.forEach(i => i.destroy());
instances = items.map(item => tippy(item));
Tippy поддерживает делегирование:
tippy(parent, {
target: '.child'
});
Если родитель удаляется без уничтожения делегированного инстанса, остаются:
Решение:
instance.destroy();
parent.remove();
Опции Tippy позволяют передавать функции:
tippy(element, {
content: () => someLargeObject.data
});
Если someLargeObject большой и продолжает ссылаться на
instance, возникает утечка.
Решение:
interactive: trueИнтерактивные tooltip создают дополнительные DOM-узлы и события:
tippy(element, {
interactive: true
});
Если tooltip остаётся “живым” после удаления элемента:
instance.destroy()Полностью удаляет:
instance.destroy();
instance.hide() vs
destroy()| Метод | Освобождает память | Удаляет DOM |
|---|---|---|
hide() |
Нет | Нет |
destroy() |
Да | Да |
import { hideAll } from 'tippy.js';
hideAll();
Не освобождает память, только скрывает tooltip.
Частая ошибка — создание tooltip внутри useEffect без
очистки:
useEffect(() => {
const instance = tippy(ref.current);
}, []);
Правильно:
useEffect(() => {
const instance = tippy(ref.current);
return () => {
instance.destroy();
};
}, []);
В директивах:
mounted(el) {
el._tippy = tippy(el);
},
unmounted(el) {
el._tippy.destroy();
}
Использование в ngOnDestroy:
ngOnDestroy() {
this.tippyInstance.destroy();
}
Вкладка Memory
Heap Snapshot
Поиск:
Detached HTMLDivElementTippy объектов без DOMtippy(document.body, {
target: '[data-tippy]'
});
Снижает количество инстансов.
tippy(element, {
onShow(instance) {
if (!instance._created) {
instance.setContent(createContent());
instance._created = true;
}
}
});
Вместо удаления/создания:
instance.setProps(newProps);
Каждый Tippy-инстанс использует Popper:
Если Popper не уничтожен:
destroy() автоматически вызывает очистку Popper.
tippy() на одном элементеtippy(element);
tippy(element);
Создаются два независимых инстанса.
window.instance = tippy(element);
Сборщик мусора не может освободить память.
При смене страниц:
destroy() перед удалением DOMПри разработке крупных приложений:
const map = new WeakMap();
function attach(el) {
const instance = tippy(el);
map.set(el, instance);
}
function detach(el) {
const instance = map.get(el);
if (instance) instance.destroy();
}
Особое внимание требуется:
В таких случаях важно:
JavaScript освобождает память только если:
Tippy-инстанс удерживает:
Если хотя бы одна ссылка остаётся — объект не удаляется.
tippy() без необходимостиfunction createTooltip(el) {
const instance = tippy(el);
return () => {
instance.destroy();
};
}
Такой подход гарантирует контроль над очисткой ресурсов.