Жизненный цикл карты в Leaflet
В библиотеке Leaflet ключевым аспектом стабильной работы в долгоживущих веб-приложениях становится корректное управление жизненным циклом объектов карты. Карта, слои, тайлы, маркеры и обработчики событий создают цепочку зависимостей, которые при неправильном обращении приводят к росту потребления памяти и деградации производительности.
Жизненный цикл карты начинается с создания экземпляра и заканчивается его полным удалением вместе со всеми связанными DOM-узлами и внутренними слушателями событий. Любые отклонения от этого сценария приводят к накоплению «висящих» ссылок.
Основной объект карты создаётся через L.map, который
связывает логическую модель с DOM-контейнером:
const map = L.map('map', {
center: [51.505, -0.09],
zoom: 13
});
При создании формируется:
Каждый добавленный объект через addTo(map)
регистрируется внутри карты и удерживается в памяти через внутренние
коллекции.
Основные источники утечек:
Layer, TileLayer,
Marker)on)Особенно критичны ситуации, когда карта уничтожается визуально, но объект продолжает существовать в памяти:
map = null; // недостаточно
Такая операция не освобождает ресурсы Leaflet.
Корректное уничтожение карты выполняется через:
map.remove();
Метод выполняет:
После вызова remove() ссылка на карту должна быть
разорвана:
map.remove();
map = null;
В SPA-приложениях это критически важно, поскольку компоненты могут пересоздаваться многократно.
Каждый слой в Leaflet является самостоятельным объектом с жизненным циклом.
const tiles = L.tileLayer(urlTemplate, {
maxZoom: 19
}).addTo(map);
При удалении карты тайлы могут продолжать догружаться, если слой не был корректно отключён. В сложных сценариях слой удаляется явно:
tiles.remove();
Маркеры удерживаются внутри внутренних структур карты:
const marker = L.marker([51.5, -0.09]).addTo(map);
Удаление:
marker.remove();
или
map.removeLayer(marker);
GeoJSON-слои часто содержат множество вложенных объектов:
const geo = L.geoJSON(data).addTo(map);
Удаление:
geo.remove();
При больших наборах данных важно избегать повторного создания без удаления предыдущего слоя.
Leaflet активно использует событийную модель:
map.on('click', onClickHandler);
Каждый обработчик удерживает ссылку на функцию, а функция часто замыкает внешние переменные, включая DOM и карту.
Удаление:
map.off('click', onClickHandler);
При использовании анонимных функций очистка становится затруднённой:
map.on('click', () => {
console.log('click');
});
Такие обработчики невозможно корректно удалить без ссылки на функцию, что приводит к накоплению утечек при пересоздании карты.
Попапы и tooltip создают временные DOM-узлы, которые могут сохраняться при неправильном управлении.
marker.bindPopup('Информация');
При удалении маркера попап может оставаться в DOM, если был открыт:
map.closePopup();
marker.remove();
Для tooltip аналогично:
map.closeTooltip();
Важно учитывать, что открытые всплывающие элементы создают дополнительные ссылки внутри системы управления слоями.
В одностраничных приложениях карта часто создаётся при монтировании компонента и уничтожается при размонтировании.
Проблемная схема:
useEffect(() => {
const map = L.map('map');
}, []);
Без cleanup-функции происходит накопление экземпляров карты.
Корректная схема:
useEffect(() => {
const map = L.map('map');
return () => {
map.remove();
};
}, []);
Дополнительный риск — повторное использование DOM-контейнера. Leaflet сохраняет привязку к DOM-элементу, поэтому повторная инициализация без удаления приводит к ошибкам и утечкам.
TileLayer активно использует кеширование изображений. Браузерный кеш в сочетании с внутренними буферами Leaflet может приводить к росту потребления памяти при частой смене слоёв.
Проблемные сценарии:
Корректная последовательность:
oldLayer.remove();
const newLayer = L.tileLayer(newUrl);
newLayer.addTo(map);
Дополнительно важно учитывать загрузку изображений, которые могут оставаться в памяти браузера до сборки мусора.
Управление памятью в Leaflet опирается на дисциплину работы с объектами:
removeLayer или
removemap.remove()Особое внимание требуется при работе с асинхронными данными: запросы, завершившиеся после удаления карты, не должны модифицировать уже уничтоженные слои или DOM.
let isDestroyed = false;
map.on('load', () => {
if (isDestroyed) return;
// безопасные операции
});
map.remove();
isDestroyed = true;
Такой подход предотвращает обращение к несуществующим объектам и снижает риск скрытых утечек памяти.