Удаление обработчиков

В интерактивных веб-картах события играют ключевую роль. Щелчки мышью, перемещение карты, изменение масштаба, загрузка слоёв и множество других действий позволяют создавать динамическое поведение приложения. Однако обработчики событий не всегда должны существовать на протяжении всего жизненного цикла объекта. Во многих сценариях возникает необходимость отключать ранее зарегистрированные обработчики.

Удаление обработчиков позволяет:

  • предотвращать повторное выполнение логики;
  • уменьшать потребление памяти;
  • избегать накопления лишних подписок;
  • временно отключать функциональность;
  • корректно уничтожать объекты и компоненты;
  • предотвращать побочные эффекты при повторной инициализации карты.

В Leaflet все объекты, поддерживающие события, наследуют возможности работы с ними через механизм Evented.


Метод off()

Основным способом удаления обработчиков является метод off().

Общая форма:

object.off(eventName, handler);

где:

  • eventName — название события;
  • handler — функция-обработчик, которая была зарегистрирована ранее.

Пример регистрации и последующего удаления:

function onMapClick(event) {
    console.log(event.latlng);
}

map.on('click', onMapClick);

map.off('click', onMapClick);

После вызова off() обработчик больше не будет реагировать на щелчки по карте.


Необходимость ссылки на функцию

Наиболее распространённая ошибка при удалении обработчиков связана с использованием анонимных функций.

Неправильный вариант:

map.on('click', function(event) {
    console.log(event.latlng);
});

map.off('click', function(event) {
    console.log(event.latlng);
});

Удаление не произойдёт, поскольку при каждом создании функции формируется новый объект функции.

Leaflet сравнивает ссылки на функции, а не их содержимое.

Правильный вариант:

function handleClick(event) {
    console.log(event.latlng);
}

map.on('click', handleClick);

map.off('click', handleClick);

Удаление нескольких событий

Одна функция может быть зарегистрирована для нескольких событий.

function updateInfo() {
    console.log('Обновление');
}

map.on('zoomend', updateInfo);
map.on('moveend', updateInfo);

Удаление выполняется отдельно для каждого события:

map.off('zoomend', updateInfo);
map.off('moveend', updateInfo);

После этого функция перестанет вызываться при масштабировании и перемещении карты.


Удаление всех обработчиков конкретного события

Если требуется удалить все подписки определённого типа, можно передать только имя события.

map.off('click');

После выполнения будут удалены все обработчики события click, независимо от того, сколько их было зарегистрировано.

Пример:

map.on('click', handler1);
map.on('click', handler2);
map.on('click', handler3);

map.off('click');

Все три обработчика будут удалены.


Удаление всех обработчиков объекта

Метод off() без параметров удаляет абсолютно все события объекта.

map.off();

После этого карта больше не будет содержать зарегистрированных пользовательских обработчиков.

Подобный вызов следует использовать осторожно, особенно в крупных приложениях, где различные модули могут подписываться на события независимо друг от друга.


Удаление обработчиков слоя

События существуют не только у карты, но и у слоёв.

Например:

const marker = L.marker([55.751244, 37.618423]).addTo(map);

function markerClick() {
    console.log('Маркер нажат');
}

marker.on('click', markerClick);

Удаление:

marker.off('click', markerClick);

Теперь нажатие на маркер больше не вызывает функцию.


Удаление обработчиков полигонов

Пример для полигона:

const polygon = L.polygon([
    [55.75, 37.61],
    [55.76, 37.64],
    [55.73, 37.65]
]).addTo(map);

function highlight() {
    polygon.setStyle({
        color: 'red'
    });
}

polygon.on('mouseover', highlight);

Отключение:

polygon.off('mouseover', highlight);

Удаление обработчиков всплывающих окон

События доступны и для всплывающих окон.

function popupOpened() {
    console.log('Окно открыто');
}

map.on('popupopen', popupOpened);

Удаление:

map.off('popupopen', popupOpened);

После удаления обработчик больше не будет реагировать на открытие всплывающих окон.


Использование контекста при удалении

Leaflet позволяет указывать контекст выполнения функции.

Регистрация:

const controller = {
    name: 'MapController',

    handleClick() {
        console.log(this.name);
    }
};

map.on('click', controller.handleClick, controller);

Удаление должно использовать тот же контекст:

map.off('click', controller.handleClick, controller);

Если контекст отличается, удаление может не сработать.


Удаление событий после однократного выполнения

Иногда требуется самостоятельно удалить обработчик после первого вызова.

function firstClick(event) {
    console.log(event.latlng);

    map.off('click', firstClick);
}

map.on('click', firstClick);

После первого клика подписка исчезнет.

Однако для таких случаев существует более удобный механизм once(), который автоматически удаляет обработчик после выполнения.

map.once('click', function(event) {
    console.log(event.latlng);
});

Удаление обработчиков при удалении слоя

При работе с динамическими слоями рекомендуется удалять подписки перед уничтожением объектов.

Пример:

function markerHandler() {
    console.log('Click');
}

marker.on('click', markerHandler);

marker.off('click', markerHandler);

map.removeLayer(marker);

Такой подход предотвращает появление «висячих» ссылок и упрощает сопровождение кода.


Удаление событий при переключении режимов интерфейса

Часто карта работает в нескольких режимах.

Например, режим просмотра:

function showInfo(event) {
    console.log(event.latlng);
}

map.on('click', showInfo);

Затем пользователь включает режим рисования.

Перед активацией нового режима старые обработчики отключаются:

map.off('click', showInfo);

После этого подключается новая логика:

function drawPoint(event) {
    L.marker(event.latlng).addTo(map);
}

map.on('click', drawPoint);

Такой подход предотвращает конфликт различных сценариев работы.


Массовое удаление обработчиков через объект событий

Leaflet позволяет регистрировать сразу несколько событий.

function zoomHandler() {
    console.log('Zoom');
}

function moveHandler() {
    console.log('Move');
}

map.on({
    zoomend: zoomHandler,
    moveend: moveHandler
});

Удаление возможно аналогичным образом:

map.off({
    zoomend: zoomHandler,
    moveend: moveHandler
});

Это удобно при большом количестве подписок.


Проверка наличия обработчиков

Для диагностики можно использовать метод listens().

if (map.listens('click')) {
    console.log('Есть обработчики');
}

После удаления:

map.off('click');

результат проверки изменится:

console.log(map.listens('click'));

Метод помогает контролировать состояние событий в сложных приложениях.


Типичные ошибки

Потеря ссылки на функцию

map.on('click', function() {
    console.log('Click');
});

Позже удалить такой обработчик невозможно без сохранённой ссылки.

Лучше:

const clickHandler = function() {
    console.log('Click');
};

map.on('click', clickHandler);

Повторная регистрация обработчиков

function update() {
    console.log('Update');
}

map.on('zoomend', update);
map.on('zoomend', update);

Функция будет вызвана дважды.

Для предотвращения подобных ситуаций перед повторной регистрацией нередко выполняют удаление:

map.off('zoomend', update);
map.on('zoomend', update);

Полное удаление всех событий

Иногда используется:

map.off();

Такой вызов может отключить обработчики, зарегистрированные другими модулями приложения.

В крупных проектах предпочтительнее удалять только конкретные подписки.


Практический пример управления режимами карты

function inspectLocation(event) {
    console.log(
        event.latlng.lat,
        event.latlng.lng
    );
}

function createMarker(event) {
    L.marker(event.latlng).addTo(map);
}

function enableInspectMode() {
    map.off('click', createMarker);
    map.on('click', inspectLocation);
}

function enableDrawMode() {
    map.off('click', inspectLocation);
    map.on('click', createMarker);
}

В данном примере карта работает в двух режимах:

  • просмотр координат;
  • создание маркеров.

При переключении режимов старые обработчики удаляются, а новые подключаются. Такой механизм является одним из наиболее распространённых сценариев применения метода off() в приложениях на основе Leaflet.


Рекомендации по организации кода

Сохранять ссылки на обработчики

const handlers = {
    click: onClick,
    zoom: onZoom
};

Это значительно упрощает последующее удаление.

Удалять события при уничтожении компонентов

map.off('click', onClick);
map.off('zoomend', onZoom);

Особенно важно для приложений на React, Vue и Angular.

Использовать once() для одноразовых действий

map.once('load', initData);

Автоматическое удаление уменьшает количество служебного кода.

Избегать глобального удаления событий

Вместо:

map.off();

предпочтительнее:

map.off('click', onClick);
map.off('zoomend', onZoom);

Такой подход делает систему событий предсказуемой, облегчает сопровождение приложения и снижает вероятность появления труднообнаружимых ошибок.