Удаление обработчиков событий в MapLibre GL JS напрямую связано с механизмом подписки и жизненным циклом карты. В библиотеке события регистрируются через методы подписки, а управление ими требует строгого контроля ссылок на функции-обработчики, поскольку именно идентичность функции определяет возможность её последующего удаления.
В MapLibre GL JS события привязываются через метод
map.on, который поддерживает несколько форм:
load, move,
zoom, click)click по слою,
mouseenter, mouseleave)sourcedata,
data)Пример регистрации обработчика:
map.on('click', (e) => {
console.log('Клик по карте', e.lngLat);
});
или с явной функцией:
function handleClick(e) {
console.log(e.lngLat);
}
map.on('click', handleClick);
Именно второй вариант критичен для дальнейшего удаления, поскольку анонимная функция теряет идентифицируемую ссылку.
Удаление выполняется через map.off, который повторяет
сигнатуру map.on. Основное правило — структура вызова
должна совпадать с регистрацией.
map.off('click', handleClick);
При этом важна строгая идентичность ссылки на функцию: новая функция с тем же телом не считается совпадением.
Глобальные события карты удаляются указанием только типа события и функции:
map.on('move', handleMove);
// удаление
map.off('move', handleMove);
Если обработчик был добавлен без сохранения ссылки (анонимно), удалить его выборочно невозможно.
map.on('move', () => {
console.log('move');
});
// такой обработчик не может быть удалён отдельно
MapLibre GL JS поддерживает привязку событий к конкретным слоям:
map.on('click', 'cities-layer', handleCityClick);
Удаление требует полного совпадения всех параметров:
map.off('click', 'cities-layer', handleCityClick);
Если указать только событие и слой, будут удалены все обработчики этого типа для слоя:
map.off('click', 'cities-layer');
Это используется для массовой очистки логики слоя без детального контроля функций.
Система допускает несколько уровней очистки:
map.off('mouseenter', 'poi-layer', handleHover);
map.off('click');
Удаляются все обработчики клика, включая глобальные и слой-ориентированные.
map.off('click', 'poi-layer');
Приводит к удалению всех кликов по слою независимо от функции.
Ключевая особенность архитектуры событий заключается в том, что идентификация обработчика основана на ссылке в памяти. Следовательно:
map.on('click', function (e) {
console.log(e);
});
Такой обработчик невозможно удалить точечно, поскольку ссылка на функцию отсутствует в явном виде.
Корректная практика заключается в сохранении ссылки:
const onCl ick = (e) => {
console.log(e);
};
map.on('click', onClick);
map.off('click', onClick);
При использовании фабрик функций создаются новые ссылки при каждом вызове:
function createHandler(prefix) {
return function (e) {
console.log(prefix, e.lngLat);
};
}
map.on('click', createHandler('A'));
Удаление в таком случае невозможно без сохранения результата вызова:
const handler = createHandler('A');
map.on('click', handler);
map.off('click', handler);
Использование bind создаёт новую функцию-обёртку, что
также влияет на удаление:
function handle(e) {
console.log(this.name, e.lngLat);
}
const bound = handle.bind({ name: 'map' });
map.on('click', bound);
map.off('click', bound);
Попытка удалить исходную функцию не сработает:
map.off('click', handle); // не совпадает с bound
При вызове map.remove() большинство внутренних
обработчиков освобождается автоматически, однако внешние ссылки на
функции остаются в пользовательском коде. Это критично в сценариях
повторного создания карты в одном контейнере.
Типичный сценарий:
map.off('click', handleClick);
map.off('move', handleMove);
map.remove();
В сложных приложениях это предотвращает накопление зависших ссылок и потенциальные утечки памяти.
В SPA-подходах обработчики часто создаются в момент монтирования компонента и должны удаляться при его уничтожении.
Логика обычно строится вокруг хранения ссылок:
let clickHandler;
function initMap(map) {
clickHandler = (e) => {
console.log(e.lngLat);
};
map.on('click', clickHandler);
}
function destroyMap(map) {
map.off('click', clickHandler);
}
Отсутствие централизованного хранения ссылок приводит к невозможности корректного освобождения событий.
При повторных вызовах map.on без удаления старых
обработчиков происходит накопление вызовов:
map.on('click', handleClick);
map.on('click', handleClick);
В результате один клик вызывает функцию несколько раз. Удаление
требует либо предварительного off, либо строгого контроля
регистрации:
map.off('click', handleClick);
map.on('click', handleClick);
В отличие от некоторых библиотек событий, MapLibre GL JS не поддерживает пространственные неймспейсы обработчиков. Невозможно сгруппировать события по идентификатору вида:
click.namespace
Поэтому управление группами обработчиков реализуется только на уровне пользовательской архитектуры — через массивы функций, структуры регистрации или отдельные менеджеры событий.
При увеличении количества обработчиков используется регистрационный слой:
const handlers = {
click: [],
move: []
};
function addHandler(map, type, fn) {
handlers[type].push(fn);
map.on(type, fn);
}
function removeAll(map, type) {
handlers[type].forEach(fn => map.off(type, fn));
handlers[type] = [];
}
Такой подход компенсирует отсутствие встроенной группировки событий.
Наиболее частые проблемы:
thisКаждая из этих ситуаций приводит к тому, что map.off не
находит совпадение и обработчик остаётся активным.
Удаление только части параметров меняет семантику очистки:
map.off('click', layerId);
В зависимости от перегрузки метода это может привести к удалению всех
обработчиков клика для слоя или к игнорированию вызова, если сигнатура
не совпадает с регистрацией. Поэтому важна симметрия вызовов
on и off.
MapLibre GL JS хранит обработчики в списках, и порядок добавления влияет на последовательность вызова, но не на удаление. Удаление выполняется поиском точного совпадения ссылки, без учёта позиции в списке.
Это означает, что один и тот же обработчик можно безопасно удалять независимо от порядка регистрации, но только при условии сохранения идентичной ссылки.
В сложных системах поверх MapLibre GL JS часто вводятся обёртки, инкапсулирующие регистрацию и удаление:
class EventManager {
constructor(map) {
this.map = map;
this.registry = new Map();
}
on(type, fn) {
this.registry.set(fn, type);
this.map.on(type, fn);
}
off(fn) {
const type = this.registry.get(fn);
if (type) {
this.map.off(type, fn);
this.registry.delete(fn);
}
}
}
Такой слой упрощает контроль жизненного цикла обработчиков и снижает вероятность утечек.
При пересоздании карты в одном DOM-контейнере старые обработчики должны быть удалены до уничтожения экземпляра или строго изолированы по экземпляру карты. В противном случае возможны ситуации, когда несколько карт одновременно реагируют на одно событие DOM, если ссылки на обработчики не были корректно освобождены.
Удаление обработчиков в MapLibre GL JS опирается на три ключевых условия:
Нарушение любого из этих условий приводит к невозможности корректного удаления и накоплению активных подписок в системе событий карты