Методы жизненного цикла контролов

Контролы в Leaflet представляют собой расширяемые UI-компоненты карты, реализованные через базовый класс L.Control. Их жизненный цикл определяется последовательностью инициализации, добавления на карту, реакции на изменение состояния карты и последующего удаления. Вся архитектура построена вокруг предсказуемых хук-методов, которые позволяют подключать пользовательскую логику в строго определённые точки работы компонента.


Базовая структура контролов

Контрол в Leaflet создаётся через наследование:

L.Control.Custom = L.Control.extend({
    initialize: function (options) {
        L.Util.setOptions(this, options);
    },

    onAdd: function (map) {
        const container = L.DomUtil.create('div', 'custom-control');
        return container;
    },

    onRemove: function (map) {
        // очистка ресурсов
    }
});

Ключевым является тот факт, что контрол не добавляется напрямую в DOM — его жизненный цикл управляется картой.


Метод initialize

Метод initialize выполняется в момент создания экземпляра контролa.

Основные задачи:

  • сохранение конфигурации;
  • подготовка внутренних переменных;
  • установка значений по умолчанию.
initialize: function (options) {
    L.Util.setOptions(this, options);
    this._buttons = [];
}

Особенности:

  • вызывается до добавления на карту;
  • не имеет доступа к DOM;
  • не взаимодействует с картой напрямую;
  • предназначен только для подготовки состояния объекта.

Метод onAdd

onAdd(map) — центральная точка жизненного цикла, вызываемая при добавлении контрола на карту.

onAdd: function (map) {
    this._map = map;

    const container = L.DomUtil.create('div', 'leaflet-control-custom');

    this._initLayout(container);
    this._bindEvents();

    return container;
}

Назначение метода:

  • создание DOM-структуры контрола;
  • привязка к экземпляру карты;
  • регистрация обработчиков событий;
  • возврат корневого DOM-элемента.

Важно:

  • возвращаемый элемент автоматически вставляется в контейнер контролов карты;
  • метод вызывается один раз при добавлении.

Метод onRemove

onRemove(map) вызывается при удалении контрола с карты.

onRemove: function (map) {
    this._unbindEvents();
    this._map = null;
}

Функции метода:

  • удаление обработчиков событий;
  • освобождение ссылок на карту;
  • очистка временных данных;
  • предотвращение утечек памяти.

Особенности:

  • DOM-элемент удаляется Leaflet автоматически;
  • разработчик отвечает только за внутренние ресурсы.

Метод addTo

Хотя addTo(map) не является методом, который переопределяется, он играет ключевую роль в жизненном цикле.

control.addTo(map);

Внутренний механизм:

  1. вызывается onAdd(map);
  2. полученный DOM-элемент добавляется в контейнер карты;
  3. контрол регистрируется внутри системы слоёв управления интерфейсом;
  4. карта инициирует события добавления.

Эквивалентный поток:

addTo(map)
   → onAdd(map)
   → DOM insertion
   → control added event

Взаимодействие с картой

При добавлении контрола происходит связывание с объектом карты:

  • this._map получает ссылку на L.Map;
  • контрол может подписываться на события карты;
  • доступна информация о текущем состоянии карты (центр, зум, слои).

Пример:

onAdd: function (map) {
    this._map = map;

    map.on('zoomend', this._update, this);

    return this._container;
}

Удаление и очистка подписок

Критически важная часть жизненного цикла — корректное отсоединение от событий.

onRemove: function (map) {
    map.off('zoomend', this._update, this);
}

Игнорирование этого шага приводит к:

  • сохранению ссылок на удалённые объекты;
  • утечкам памяти;
  • некорректному поведению при повторном добавлении.

Внутренние методы и расширения жизненного цикла

В контролах часто используются вспомогательные методы, влияющие на поведение жизненного цикла:

_initLayout

Отвечает за построение DOM-структуры.

_initLayout: function () {
    this._container = L.DomUtil.create('div', 'leaflet-control');
}

Вызывается внутри onAdd, но не является частью публичного API.


_update

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

_update: function () {
    const zoom = this._map.getZoom();
    this._container.innerHTML = zoom;
}

_setPosition

Для контролов с изменяемым положением:

_setPosition: function (position) {
    L.DomUtil.removeClass(this._container, 'leaflet-top leaflet-bottom');
    L.DomUtil.addClass(this._container, position);
}

События жизненного цикла контролов

Контролы могут генерировать и обрабатывать события:

  • add — после добавления на карту;
  • remove — после удаления;
  • пользовательские события DOM;
  • события карты.

Пример:

map.on('controladd', function (e) {
    console.log(e.control);
});

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

Контрол может быть удалён и добавлен повторно:

map.removeControl(control);
map.addControl(control);

Поведение:

  • onRemove вызывается при удалении;
  • при повторном добавлении создаётся новый DOM через onAdd;
  • состояние объекта контролa сохраняется между операциями.

Особенности управления состоянием

При проектировании контролов важно разделять:

  • постоянное состояние (options, конфигурация);
  • временное состояние (DOM, события карты).

Правильная архитектура:

  • initialize — конфигурация;
  • onAdd — создание интерфейса;
  • onRemove — очистка;
  • вспомогательные методы — обновление и логика.

Типичные ошибки жизненного цикла

Часто встречающиеся проблемы:

  • сохранение DOM вне onAdd;
  • отсутствие удаления обработчиков событий;
  • попытка доступа к map до onAdd;
  • логика UI в initialize;
  • повторное создание слушателей без очистки.

Порядок выполнения методов

Типичный цикл контролa:

initialize(options)
        ↓
addTo(map)
        ↓
onAdd(map)
        ↓
[работа контролa]
        ↓
onRemove(map)

Этот порядок определяет всю модель поведения расширяемых UI-компонентов Leaflet и является основой для построения сложных интерфейсных элементов на карте.